I have an api server and some script jobs. They are using the same function to pull a roster using mongoose and populate the players in the roster.
On the api server, this function is called normally. Using the script, it doesn't.
API example
function getRoster(id) {
var deferred = Q.defer();
Roster.find({_id:id}, 'playerRoster userId tournamentId').populate('playerRoster').exec(
function(err, roster) {
if (err) {
deferred.resolve(err);
}
deferred.resolve(roster[0]);
});
return deferred.promise;
}
api.post('/get_roster', function(req, res) {
// get tournament
var id = req.body._id;
var playerId = req.body.playerId;
getRoster(id).then(function(data) {
var roster=data;
res.json(roster);
});
});
Script
module.exports = function(config) {
this.getRoster=function(id) {
//return Roster.find({_id:id}, 'playerRoster userId tournamentId').exec( - THIS RETURNS
return Roster.find({_id:id}, 'playerRoster userId tournamentId').populate('playerRoster').exec(
function(err, roster) {
if (err) {
return err;
}
console.log('roster[0]',roster);
return roster[0];
});
}
this.tallyPoints = function(tournamentPlayer,sportsPlayers) {
var deferred = Q.defer();
var totalPoints =0;
console.log("tallyPoints 0 ",tournamentPlayer);
var rosterId = tournamentPlayer.player.roster[0];
console.log("tallyPoints 1 ",rosterId);
this.getRoster(rosterId).then(function(roster2){
console.log("tallyPoints 2 ",roster2);
...
deferred.resolve(totalPoints);
});
return deferred.promise;
}
return this;
};
In the script, neither logging for the roster[0] or tallyPoints 2 lines print, but there is no error either.
Why doesn't Roster.find return when I add populate? The only thing I can imagine is because playerRoster collection has 2000 records searching for ~10 and it hits some timeout that isn't being caught.
Any suggestion to clean it up is also appreciated.
Thanks
Moongoose supports promises for a long time. It's unsuitable to use callback-based Mongoose API where promises are desirable and the use of Q.defer with existing promises is known as Deferred antipattern (similarly, new Promise results in promise construction antipattern).
In its current state getRoster doesn't return a promise and doesn't handle errors correctly.
function getRoster(id) {
return Roster.find({_id:id}, 'playerRoster userId tournamentId').populate('playerRoster').exec()
.then(roster => roster[0]);
}
api.post('/get_roster', function(req, res) {
// get tournament
var id = req.body._id;
var playerId = req.body.playerId;
getRoster(id)
.then(function(data) {
var roster=data;
res.json(roster);
})
.catch(err => {
// handle error
});
});
Considering that only roster[0] is used, it likely should be changed to Roster.findOne.
It doesn't matter whether getRoster is used in Express route or elsewhere, it should work. It's unknown how module.exports = function(config) {...} module is used, but this may refer to wrong context if it isn't used as class. If getRoster and tallyPoints don't use config, they shouldn't reside inside this function.
I'm new to Nodejs, Express and Leveldb.
I created db using level and want to pass parameter.
exports.index = function(req, res) {
var models_array = [];
db.models.createValueStream()
.on('data', function (data) {
console.log(data.name);
models_array.push(data.name);
console.log(models_array); // 1st
});
console.log(models_array); //2nd
res.render('home', {
title: 'Home',
models:models_array
});
};
This is my code but 2nd console.log(models_array) is returning null because they are running asynchronously.
Even 1st console.log is returning what I expected.
How can make this work properly?
So that I can pass proper data to template.
I found myself. I can use .once()
var models_array = [];
db.models.createValueStream()
.on('data', function (data) {
models_array.push(data.name);
}).once('end', function() {
res.render('home', {
title: 'Home',
models:models_array
});
});
I am fairly new to NodeJS and asynchronous programming. I've never been seriously into JavaScript until I started working on a project that required me to set up a REST API using node server and I started working on it for the past day or two. I'm trying to set up a modular application and have been successful in setting up a route, model and controller using Express JS.
I use express to read the request bodies to get data posted to the server in JSON. But my current situation came up when I tried to pass certain data from my routes module (or file) to my controller in a function call.
When I couldn't read the data I tried to log it and to my surprise the JSON Object that I passed as an argument is now logged as a [Function].
Here is how I pass the data:
routes.js
var express = require('express');
var UserCtrl = require('./controller');
var user = express.Router();
// Define routes
// Request to list all users.
user.get('/', function (req, res){
UserCtrl.getAll(function (result){
res.json(result);
});
});
user.post('/create', function (req, res){
var postData = {
'firstName': req.body.firstName
};
UserCtrl.create(function (postData, result){
res.json(result);
});
});
module.exports = user;
controller.js
var Users = require('./model');
// list all users
module.exports.getAll = function (callback) {
Users.findAll(callback);
}
// create user
module.exports.create = function (postData, callback) {
console.log(postData);
// create user by passing the user to a model
Users.create (newUser, callback);
}
May be I've got the basics wrong or may be this is completely a mistake of my code. Should I make the controller also an express middleware?
This code calls create. The first value sent to it is a function. This function that you send to create takes in a postData and a result value.
UserCtrl.create(function (postData, result){
res.json(result);
});
This code is what is getting called. The first thing that gets sent in is postData. When you called it just above you sent in a function to that first thing. So console.log will output function.
module.exports.create = function (postData, callback) {
console.log(postData);
// create user by passing the user to a model
Users.create (newUser, callback);
}
Perhaps you meant to send in that postData object first?
user.post('/create', function (req, res){
var postData = {
'firstName': req.body.firstName
};
// This is the changed line
UserCtrl.create(postData, function (postData, result){
res.json(result);
});
});
is there a way to export some variables that are inside a callback function? for example, if i need to use room.room_id in another file, what should i do? i tried module.exports.roomId = room.room_id but roomId in another file appeared to be undefined.thanks!
var Room = require('../models/database').Room
exports.create = function (req, res) {
Room
.create({
room_name: req.body.roomName
})
.complete(function () {
Room
.find({where: {room_name: req.body.roomName}})
.success(function (room) {
// if(err) console.log(err);
res.redirect('rooms/videochat/' + req.body.roomName + '/' + room.room_id);
console.log("room_id: " + room.room_id);
module.exports.roomId = room.room_id;
})
})
};
You can't do it like that because modules are evaluated synchronously and you're mutating module.exports some time in the future. What you need to do is supply a callback and either pass the value in or use the callback as an indicator that you can successfully read from the exported property.
This is not the best way to solve this problem, because modules are read once synchronously and cached but your code seems to handle requests and responses.
You will want rather export something like this:
var rooms = {};
exports.create = function (req, res, next) {
Room.create({
room_name: req.body.roomName
}).complete(function () {
Room.find({where: {room_name: req.body.roomName}})
.success(function (room) {
res.redirect('rooms/videochat/' + req.body.roomName + '/' + room.room_id);
rooms[req.body.roomName] = room.room_id;
});
});
};
exports.rooms = rooms;
If you are using Express.js, you can register in another place a route like this:
var roomsManager = require('./path/to/the/module');
//handle the create room endpoint
app.post('/room', roomsManager.create);
//get the room_id given a room name:
console.log('the room id of "some room" is:', roomsManager.rooms["some room"]);
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
});