Node.js code hints - node.js

Hello I would like to know if I'm right with this kind of code and if there is a better way to do it.
There is a module to get the homepage.
app.js:
[...]
// Get homepage
app.get('/', frontend.index);
[...]
The frontend.index function have to get a couple of data obj from redis (util.getRemoteConfig and util.getGlobalStats) and use them to render the Jade template, so I did this.
frontend.js:
[...]
// [GET] Homepage
exports.index = function(req, res){
var remoteConfig = {};
var globalStats = {};
util.getRemoteConfig(function(remoteConfig) {
var version = 'n.a.';
if (remoteConfig.version)
version = remoteConfig.version;
util.getGlobalStats(function(globalStats) {
res.render('index.jade',
{ title: config.items.default_title, version: version, global_stats: JSON.stringify(globalStats) }
);
});
});
};
[...]
The util module gets the data from redis and pass it via callback.
util.js:
[...]
exports.getRemoteConfig = function(cb) {
client.hget('remote_config', function(err, obj) {
if (err) return cb(err);
// do something with obj and return it
cb(obj);
});
};
exports.getGlobalStats = function(cb) {
client.hgetall("global_stats", function (err, obj) {
if (err) return cb(err);
// do something with obj and return it
cb(obj);
});
};
[...]
This is working fine but is it really correct? Can I do something better than this?
Thanks any hint will be useful.

If remote config and global stats are route independent, you should probably put them into a middle-ware instead of controller:
middlewares.js:
exports.remoteConfig = function(req,res,next){
util.getRemoteConfig(function(err,config){
if(err){
return next(err);
}
req._remote_config = config;
return next(null);
});
}
controllers.js:
exports.index = function(req, res){
var remoteConfig = req._remote_config;
var globalStats = req._global_stats;
res.render('index.jade', {
title: config.items.default_title,
version: version,
global_stats: JSON.stringify(globalStats)
};
};
app.js:
app.get('/', middleware.remoteConfig, middleware.globalStats,controllers.index);
or
app.use(middleware.remoteConfig);
app.use(middleware.globalStats);
if the middleware should be used for all routes.
Note: the code is untested.

Related

Expressjs. TypeError when call function in constructor from prototype function

I was trying to call a function in the constructor from prototype but keep getting the below error and I dont know whats wrong with my code.
TypeError: this.authorize is not a function
This is my code:
controller.js
var Controller = function() {
this.authorize = function(req, res) {
if (!req.user) {
res.redirect("/");
}
};
};
Controller.prototype.online = function(req, res) {
this.authorize(req, res);
res.render('./play/online');
};
var controller = new Controller();
module.exports = controller;
route.js
var router = require('express').Router();
var controller = require('../controller');
router.get('/online', controller.online);
module.exports = router;
If I put authorize function outside of Controller then I can call it but I don't want do that.
So what can I do?
Update:
This error occurs in Nodejs when I apply the request "/online", not in pure Javascript
You are loosing context when passing online as a callback
router.get('/online', controller.online.bind(controller));
Or inside constructor
var Controller = function() {
this.authorize = function(req) {
console.log(req);
};
this.online = this.online.bind(this);
};
Set the authorize function on the prototype of Controller like you did with the online function.
Edit: I tested your code (not using Controller.prototype) and it works for me...
I can call authorize within the online function. Does the error occur when you call authorize from the online function or does it occur somewhere else? Are you sure there isn't a typo in your code?
Could you try to define your online function in the constructor as well?
//Your initial version: works for me...
var Controller = function() {
this.authorize = function(req) {
console.log(req);
};
};
Controller.prototype.online = function(text) {
this.authorize(text);
};
var controller = new Controller();
controller.online("Some text");
//My prototype version: works as well...
var Controller2 = function() {};
Controller2.prototype.authorize = function(req) {
console.log(req);
};
Controller2.prototype.online = function(text) {
this.authorize(text);
};
var controller2 = new Controller2();
controller2.online("Some text2");

express & mongoose - Cannot call method 'get' of undefined - using res.json()

and thanks to be there.
Issue :
I'm making a tiny mongoose "middleware" to handle a mongoose error :
// callback function called at each mongoDB response
var handleDbRes = function(callback) {
return function (err, entries) {
if (err) {
err.status = 500;
return next(err);
}
return callback(entries) // that line throw the exception
}
};
And so I'm using it into an api endpoint, e.g. :
someRouter.get('/', function(req, res) {
models.article.find(handleDbRes(res.json))
})
With that code, I encounter an error :
TypeError: Cannot call method 'get' of undefined
I followed the exception and looked at res.json() declaration, when debugging, I figured out :
var app = this.app;
var *** = app.get('***') // that line throw the exception
I guess that app is not defined cause app doesn't exists in "this".
Please can you help me to solve this problem ? I think that the reason is simple but I don't get it...
Thanks you for listening ;)
EDIT : I tried to res.json.bind(res) and it worked, as I thought, but that's really awful to bind this way for most api endpoint and I guess there is another way to do that kind of functionality without that.
EDIT : Thanks to Mscdex advices, I modified my code this way :
.get('/', function(req, res, next) {
models.article.find(handleDbRes(res.json.bind(res), next))
...
...
// callback function called at each mongoDB response
var handleDbRes = function(successCallback, errorCallback) {
return function (err, entries) {
if (err) {
err.status = 500;
return errorCallback(err);
}
return successCallback(entries)
}
};
When you pass res.json, the context for the json() function is lost (it no longer knows what this is because it is not bound). So here are a few possible solutions:
Use a bound version of the function so that this inside json() will always evaluate correctly:
someRouter.get('/', function(req, res) {
models.article.find(handleDbRes(res.json.bind(res)))
})
Or use a wrapper function instead:
someRouter.get('/', function(req, res) {
function respondJSON(val) {
res.json(val);
}
models.article.find(handleDbRes(respondJSON))
})
Or just pass in res and call res.json() inside handleDbRes():
someRouter.get('/', function(req, res) {
models.article.find(handleDbRes(res))
})
// callback function called at each mongoDB response
var handleDbRes = function(res) {
return function(err, entries) {
if (err) {
err.status = 500;
return next(err);
}
res.json(entries);
}
};
The other problem is that handleDbRes() doesn't have access to next, so you need to also pass that function in for when you run into an error.

Dynamic routes with Express.js -- is this even possible?

Every time I update the database with a new menu item, I'm trying to get the routing to update with one more route. Here's my sad little ugly attempt:
Here in app.js, I check the menu database and shazaam...routes are made on the fly at startup. Cool!:
// in app.js //
var attachDB = function(req, res, next) {
req.contentdb = db.content;
req.menudb = db.menu;
req.app = app; // this is the express() app itself
req.page = PageController;
next();
};
db.menu.find({}, function (err, menuitems){
for(var i=0; record = menuitems[i]; i++) {
var menuitem = record.menuitem;
app.all('/' + menuitem, attachDB, function(req, res, next) {
console.log('req from app all route: ',req)
PageController.run(menuitem, req, res, next);
});
}
http.createServer(app).listen(config.port, function() {
console.log(
'\nExpress server listening on port ' + config.port
);
});
});
Not real elegant but it's a proof of concept. Now here's the problem: When I save a new menu item in my Admin.js file, the database get's updated, the router seems to get updated but something about the request just blows up after clicking on a menu link with a dynamically created route
Many things in the request seem to be missing and I feel like there is something fundamental I don't understand about routing, callbacks or perhaps this is just the wrong solution. Here's what the function responsible for creating a new menu item and creating a new route in my Admin.js file looks like:
// in Admin.js //
menuItem: function(req, res, callback) {
var returnMenuForm = function() {
res.render('admin-menuitem', {}, function(err, html) {
callback(html);
});
};
var reqMenudb = req.menudb,
reqContentdb = req.contentdb,
reqApp = req.app,
reqPage = req.page;
if(req.body && req.body.menuitemsubmitted && req.body.menuitemsubmitted === 'yes') {
var data = { menuitem: req.body.menuitem };
menuModel.insert( data, function(err) {
if (err) {
console.log('Whoa there...',err.message);
returnMenuForm();
} else {
// data is inserted....great. PROBLEM...the routes have not been updated!!! Attempt that mimics what I do in app.js here...
reqApp.all('/' + data.menuitem, function(req, res, next) {
// the 2 db references below are set with the right values here
req.contentdb = reqContentdb;
req.menudb = reqMenudb;
next();
}, function(req, res, next) {
reqPage.run(data.menuitem, req, res, next);
});
returnMenuForm();
}
});
} else {
returnMenuForm();
}
},
Saving the data in the admin section works fine. If you console log app.routes, it even shows a new route which is pretty cool. However after refreshing the page and clicking the link where the new route should be working, I get an undefined error.
The admin passes data to my Page controller:
// in PageController.js //
module.exports = BaseController.extend({
name: "Page",
content: null,
run: function(type, req, res, next) {
model.setDB(req.contentdb); /* <-- problem here, req.contentdb is undefined which causes me problems when talking to the Page model */
var self = this;
this.getContent(type, function() {
var v = new View(res, 'inner');
self.navMenu(req, res, function(navMenuMarkup){
self.content.menunav = navMenuMarkup;
v.render(self.content);
});
});
},
getContent: function(type, callback) {
var self = this;
this.content = {}
model.getlist(function(records) {
if(records.length > 0) {
self.content = records[0];
}
callback();
}, { type: type });
}
Lastly, the point of error is here in the model
// in Model.js //
module.exports = function() {
return {
setDB: function(db) {
this.db = db;
},
getlist: function(callback, query) {
this.db.find(query || {}, function (err, doc) { callback(doc) });
},
And here at last, the 'this' in the getlist method above is undefined and causes the page to bomb out.
If I restart the server, everything works again due to my dynamic loader in app.js. But isn't there some way to reload the routes after a database is updated?? My technique here does not work and it's ugly to be passing the main app over to a controller as I'm doing here.
I would suggest two changes:
Move this menu attachment thing to a separate module.
While you're at it, do some caching.
Proof of concept menu db function, made async with setTimeout, you'll replace it with actuall db calls.
// menuitems is cached here in this module. You can make an initial load from db instead.
var menuitems = [];
// getting them is simple, always just get the current array. We'll use that.
var getMenuItems = function() {
return menuitems;
}
// this executes when we have already inserted - calls the callback
var addMenuItemHandler = function(newItem, callback) {
// validate that it's not empty or that it does not match any of the existing ones
menuitems.push(newItem);
// remember, push item to local array only after it's added to db without errors
callback();
}
// this one accepts a request to add a new menuitem
var addMenuItem = function(req, res) {
var newItem = req.query.newitem;
// it will do db insert, or setTimeout in my case
setTimeout(function(newItem){
// we also close our request in a callback
addMenuItemHandler(newItem, function(){
res.end('Added.');
});
}, 2000);
};
module.exports = {
addMenuItem: addMenuItem,
getMenuItems: getMenuItems
}
So now you have a module menuhandler.js. Let's construct it and use it in our app.
var menuHandler = require('./menuhandler');
var app = express();
// config, insert middleware etc here
// first, capture your static routes - the ones before the dynamic ones.
app.get('/addmenuitem', menuHandler.addMenuItem);
app.get('/someotherstaticroute', function(req, res) {
var menu = menuHandler.getMenuItems();
res.render('someview', {menu: menu});
});
// now capture everything in your menus.
app.get('/:routename', function(req, res){
// get current items and check if requested route is in there.
var menuitems = menuHandler.getMenuItems();
if(menuitems.indexOf(req.params.routename) !== -1) {
res.render('myview', {menu: menuitems});
} else {
// if we missed the route, render some default page or whatever.
}
});
app.get('/', function(req, res) {
// ...
});
Now you don't go to db if there were no new updates (since menuitems array is always up to date) so your initial view is rendered faster (for that 1 db call, anyway).
Edit: oh, I just now saw your Model.js. The problem there is that this refers to the object you have returned:
{
setDB: function(db) {
this.db = db;
},
getlist: function(callback, query) {
this.db.find(query || {}, function (err, doc) { callback(doc) });
}
}
So, no db by default. And since you attach something to the app in the initial pageload, you do get something.
But in your current update function, you attach stuff to the new app (reqApp = req.app), so now you're not talking to the original app, but another instance of it. And I think that your subsequent requests (after the update) get the scope all mixed up so lose the touch with the actual latest data.
In your code when you start your server it reads from the menu db and creates your routes. When your menu changes, you do not re-read from db again.
I suggest you do something like the following
app.all('*', function(req, res) {
//read from your menu db and do the the route management yourself
});

Send multiple DB query results to a single view using Express

I have a dashboard view ( dashboard.jade ) that will display two panels with different information, all that info should be retrieved from a database and then sent to the view.
Let's say i have a route file ( document.js ) with two actions defined:
exports.getAllDocuments = function(req, res){
doc = db.model('documents', docSchema);
doc.find({}, function(err, documents) {
if (!err) {
// handle success
}
else {
throw err;
}
});
};
exports.getLatestDocumentTags = function(req, res){
tags = db.model('tags', tagSchema);
tags.find({}, function(err, docs) {
if (!err) {
// handle success
}
else {
throw err;
}
});
};
These functions would only serve the porpuse of retrieving data from the database.
Now i would like to send that data to the dashboard view from my dashboard.js route file under exports.index function where i render my dashboard view.
The problem is, since the db calls will be async i wouldn't have access to the data before i could call the view.
I guess i could have an action that simply did all my db calls and through callbacks deliver all the data at once to the view but that would make my data retrieval actions not reusable.
I'm really confused on how to tackle this problem correctly, probably i'm getting this async thing all wrong. Can someone give me some hints on how to do this properly ?
Here's something to pique your interest.
//Check out the async.js library
var async = require('async');
//Set up your models once at program startup, not on each request
//Ideall these would be in separate modules as wel
var Doc = db.model('documents', docSchema);
var Tags = db.model('tags', tagSchema);
function index(req, res, next) {
async.parallel({ //Run every function in this object in parallel
allDocs: async.apply(Doc.find, {}) //gets all documents. async.apply will
//do the equivalent of Doc.find({}, callback) here
latestDocs: async.apply(Tags.find, {})
], function (error, results) { //This function gets called when all parallel jobs are done
//results will be like {
// allDocs: [doc1, doc2]
// latestDocs: [doc3, doc4]
// }
res.render('index', results);
});
}
exports.index = index;
};
Try some more tutorials. If you haven't had the "a ha" moment about how async programming works in node, keep going through guided, hand-held tutorials before trying to write brand new programs without guidance.
//Check out the async.js library and mangoose model
var mongoOp = require("./models/mongo");
var async = require('async');
router.get("/",function(req,res){
var locals = {};
var userId = req.params.userId;
async.parallel([
//Load user Data
function(callback) {
mongoOp.User.find({},function(err,user){
if (err) return callback(err);
locals.user = user;
callback();
});
},
//Load posts Data
function(callback) {
mongoOp.Post.find({},function(err,posts){
if (err) return callback(err);
locals.posts = posts;
callback();
});
}
], function(err) { //This function gets called after the two tasks have called their "task callbacks"
if (err) return next(err); //If an error occurred, we let express handle it by calling the `next` function
//Here `locals` will be an object with `user` and `posts` keys
//Example: `locals = {user: ..., posts: [...]}`
res.render('index.ejs', {userdata: locals.user,postdata: locals.posts})
});

Node.js Error Handling

I have the following code to insert data into DB - this code has to be executed in a sequential order
Router JS
module.exports = function(app) {
app.get('/registerUser', function(req, res ) {
objuser.userName = 'testuser';
objuser.password = 'password';
objuser.status = true;
registerUser (objuser ); //calls Business.js
res.OK();
res.end ();
});
}
Business.js
var registerUser = function (objuser )
{
userDB.registerUser (objuser ) ; //calls db.js
};
db.js
exports.registerUser = function (objUser )
{
var User = db.model(strCollectionName, UserSchema );
var objSchema = new User(objUser);
objSchema.save(function (err)
{
if (err)
console.error (err);
else
console.log ("registerUser : Data insertion success.");
});
}
In the db.js Im getting error from Mongo if I try to insert duplicate value. I wan to pass the error message to HTML page to display the same. What should I do? I tried
throw Error (err)
But it breaks the server.
Assuming you are using expressjs, I'd make use of the next callback. like so:
app.get('/registerUser', function (req, res, next) {
objuser.userName = 'testuser';
objuser.password = 'password';
objuser.status = true;
registerUser(objuser, function (err) {
if(err) {
//this will be handled by express's errorHandler or whatever you have configured
return next(err);
}
//do whatever you want to do with the response
});
});
If you don't want to make your Business.js call async then you will obviously change this code to a try...catch flow. Node.js apps are happier using async calls though, so a common convention in nodejs apps is to expose a callback using the (err, result) parameters. So your db.js call would be :
exports.registerUser = function (objUser, callback )
{
var User = db.model(strCollectionName, UserSchema );
var objSchema = new User(objUser);
objSchema.save(function (err) {
if (err) return callback(err);
return callback(null, objSchema);
});
}
By now you probably notice that your Business.js call would just be a mediator between your route and your db code...whether you need it or not is up to you.
HTH,
Mike
If you are using node.js >= 0.8.x you can use connect-domain middleware that adds new domain functionality to your express/connect application. With doamin module you don't need to pass error up manually. You can simple throw error and it will be passed to error handler automatically.

Resources