I have a get in my app.js
app.get('/api/personnel', api.personnel);
that calls this function as a callback to load some data from mongo:
exports.personnel = function(req, res) {
var docs;
db.personnel.find(function(err, docs) {
if (err) {
logError(err);
} else {
res.json({
personnel: docs
});
}
});
};
That works just fine, but I'd really like to be able to call a callback for testing purposes when the function is complete:
exports.personnel = function(req, res, callback) {
var docs;
db.personnel.find(function(err, docs) {
if (err) {
logError(err);
} else {
res.json({
personnel: docs
});
}
callback();
});
callback() is empty when the function is called from the live application and gives me a error:
Error: Can't set headers after they are sent.
How do I go about having a get call my callback?
You can just wrap that function to insert the additional function argument:
exports.personnel = function(req, res, callback) {
var docs;
db.personnel.find(function(err, docs) {
if (err) {
logError(err);
} else {
res.json({
personnel: docs
});
}
});
///////////////////////////////////////////////////
var callback = ...;
pp.get('/api/personnel', function(req, res) {
api.personnel(req, res, callback);
});
third arity in Express is always reserved for next() callback (as found in middlewares).
If you want to have "callback" but does not want to mess up with express, let's hack!
exports.personnel = function(req, res, callback) {
var docs;
db.personnel.find(function(err, docs) {
if (err) {
logError(err);
} else {
res.json({
personnel: docs
});
}
if(process.env.NODE_ENV === 'test')
callback();
});
then, when you want to test, export NODE_ENV=test in your shell
Related
I'm trying to reuse my controllers which handle database operations. I'm bit struggling with structuring my application. Here's what I have:
server.js
var apiController = require('./controllers/api');
router.get('/cars', function (req, res) {
// get all cars from DB and render view
apiController.getCars().then(function (cars) {
res.render('index', {cars: cars});
});
});
router.get('/api/cars', function (req, res) {
// get all cars from DB and return JSON
apiController.getCars().then(function (cars) {
res.json(cars);
});
});
controllers/api.js
module.exports = {
getCars: function () {
db.collection('cars').find().toArray( function (err, cars) {
if (err) throw err;
return cars;
});
},
// tried also something like this but this doesn't really work
// for my use case because I don't want to attach any particular
// res to the function
getCars: function (req, res, next) {
db.collection('cars').find().toArray( function (err, cars) {
if (err) throw err;
res.json(cars);
});
},
};
Your current problem is that you expect promises as return in server.js while you use callbacks in the controller. I suggest you change your function getCars to return a Promise. Don't know what ODM/ORM you're using but it might look like something like this:
getCars: function () {
return db.collection('cars').find();
},
server.js
var apiController = require('./controllers/api');
router.get('/cars', function (req, res) {
apiController.get('cars').then(function (cars) {
res.render('index', {cars: cars});
});
});
router.get('/api/cars', function (req, res) {
apiController.get('cars').then(function (cars) {
res.json(cars);
});
});
controllers/api.js
var Promise = require('bluebird');
module.exports = {
get: function (modelName) {
return new Promise(function(resolve,reject){
return db.collection(modelName).find().toArray(function(err, models){
if (err) {
return reject(err);
}
else {
return resolve(models);
}
});
});
}
};
server.js
var apiController = require('./controllers/api');
router.get('/cars', apiController.getCars);
controllers/api.js
function getCarsAsync(req, res, next){
db.collection('cars').find().then(function(carsData){
if(carsData){
return res.send(carsData);
}
else{
return res.status(401).send('User is not authorized');
}
}).catch(function(err){
return next(err);
});
}
module.exports = {
getCars: getCarsAsync
};
I use ExpressJS for routing, and bluebird for Promises.
I have the following code repeating for few routes, and in the end they all have the same .catch function, with responds with a json of failure.
router.get('/', function(req, res) {
return somePromise
.then(function doesSomething(someVariable) {
doSomething;
})
.catch(function catchesError(err) {
return res.json({ success: false });
});
});
I would like to extract the catchesError function, but then it wouldn't be able to use the res object.
Any suggestions?
Just create a function and pass the res object as an argument and return a function.
function makeErrorCatcher(res) {
return function catchesError(err) {
return res.json({
success: false
});
}
}
router.get('/', function (req, res) {
return somePromise
.then(function doesSomething(someVariable) {
doSomething;
})
.catch(makeErrorCatcher(res));
});
You can decorate .get to pass to have a default catch handler. (Assuming you're not interested in a custom router):
Object.keys(router).forEach(function(key){ // for each method
router[key+"P"] = function(path, fn){ // create a decorated alt
router[key].call(router, function(req, res, next){ // delegate
var that = this, args = arguments;
return Promise.try(function(){ // wrap to make throw safe
return fn.apply(that, args); // delegation
}).catch(function catchesError(err){
// LOG YOUR ERRORS, DON'T CATCH ALL
return res.json({ success: false });
});
});
};
});
This would let you do:
router.getP('/', function(req, res) {
return somePromise.then(function doesSomething(someVariable) {
doSomething;
});
});
Which will now catch errors automatically and send the appropriate JSON. Eliminating duplication or the possibility to forget errors altogether.
Pretty much like Ben Fortune's solution, using bind():
function catchesError(res, err) {
return res.json({ success: false });
}
router.get('/', function(req, res) {
return somePromise
.then(function doesSomething(someVariable) {
doSomething;
})
.catch(catchesError.bind(null, res));
});
Replace null with this if you're in a class.
If you're not planning in using promise chainging, you can add a catcher function that attaches the catch handler:
function catcher(promise, req, res) {
promise.catch(function catchesError(err) {
return res.json({ success: false });
});
return promise;
}
router.get('/', function(req, res) {
return catcher(somePromise, req, res)
.then(function doesSomething(someVariable) {
doSomething;
});
});
However if you want to benefit the awesome promise chaining mechanism, then you need to manually call the catch handler to make sure it's the last in the chain:
function makeCatchHandler(res) {
return function(err) {
return res.json({ success: false });
};
}
router.get('/', function(req, res) {
return catcher(somePromise, req, res)
.then(function doesSomething(someVariable) {
doSomething;
}).catch(makeCatchHandler(res));
});
I'm a beginner in Node/Express. I tried to make an CRUD application but stuck at update and delete. I think my router code is problematic but I don't know why. The following code is in my controller, everything works but PUT and DELETE. It always route to GET. I tried to use next(); but it returns this error: Can't set headers after they are sent..
I can make the delete works by using GET /:company_id/delete but it's not a good and standardized solution. How can I get update and delete process worked?
'use strict';
var Companies = require('../../models/companies');
module.exports = function (router) {
// INDEX
// accessed at GET http://localhost:8000/companies
router.get('/', function (req, res) {
Companies.find(function(err, model) {
if (err) {
res.send(err);
}
else {
res.format({
json: function () {
res.json(model);
},
html: function () {
res.render('companies/index', model);
}
});
}
});
});
// CREATE VIEW
// accessed at GET http://localhost:8000/companies/create
router.get('/create', function (req, res) {
res.render('companies/create');
});
// CREATE DATA
// accessed at POST http://localhost:8000/companies
router.post('/', function (req, res) {
var name = req.body.name && req.body.name.trim();
var type = req.body.type && req.body.type.trim();
// VALIDATION
if (name === '') {
res.redirect('/companies/create');
return;
}
var model = new Companies({name: name, type: type});
model.save(function(err) {
if (err) {
res.send(err);
}
else {
res.redirect('/companies');
}
});
});
// READ
// accessed at GET http://localhost:8000/companies/:company_id
router.get('/:company_id', function(req, res) {
Companies.findById(req.params.company_id, function(err, model) {
if (err) {
res.send(err);
}
else {
res.render('companies/read', model);
}
});
});
// UPDATE VIEW
// accessed at GET http://localhost:8000/companies/:company_id/edit
router.get('/:company_id/edit', function(req, res) {
Companies.findById(req.params.company_id, function(err, model) {
if (err) {
res.send(err);
}
else {
res.render('companies/edit', model);
}
});
});
// UPDATE DATA
// accessed at PUT http://localhost:8000/companies/:company_id
router.put('/:company_id', function(req, res) {
Companies.findById(req.params.company_id, function(err, model) {
if (err) {
res.send(err);
}
else {
model.name = req.body.name;
model.type = req.body.type;
model.save(function(err) {
if (err) {
res.send(err);
}
else {
res.redirect('/companies');
}
});
}
});
});
// DELETE
// accessed at DELETE http://localhost:8000/companies/:company_id
router.delete('/:company_id', function (req, res) {
Companies.remove({ _id: req.params.company_id }, function(err) {
if (err) {
res.send(err);
}
else {
res.redirect('/companies');
}
});
});
};
HTML forms only support GET and POST. XMLHTTPRequest supports PUT and DELETE however, so you may have to go that route OR use something like method-override to allow HTML forms to submit using other HTTP verbs.
I'm using express and mongoose. I have a weird issue when I'm using mocha test to run this endpoint.
exports.broadcastMessages = function(req, res, next) {
User.find({}, function(err, users) {
if(err) return next(err);
var push = function(user, callback) {
user.messages.push(req.body.message);
user.save(function(err) {
callback(err);
});
};
var fin = function(err) {
if (err) {
return next(err);
}
console.log('aaaaaaaaaa');
return res.send('ok');
};
async.each(users, push, fin);
});
};
Then I got a timeout error. There is only one user. So it's not a time issue. And I'm sure res.send('ok') was called. But when I removed user.save(). It worked...
exports.broadcastMessages = function(req, res, next) {
User.find({}, function(err, users) {
if(err) return next(err);
var push = function(user, callback) {
user.messages.push(req.body.message);
callback(err);
};
var fin = function(err) {
if (err) {
return next(err);
}
console.log('aaaaaaaaaa');
return res.send('ok');
};
async.each(users, push, fin);
});
};
I don't know why. Why added one more user.save() it doesn't work? res.send is called but no response.
The version of express is 3.4.7. Mongoose is 3.8.2.
When you say "I got a timeout error" do you mean mocha failed your test for taking too long? If so that is probably a problem in your mocha test itself not calling done() correctly. The above code looks OK to me and I think it should work. Some misc points:
Whenever you have this pattern:
user.save(function(err) {
callback(err);
});
You don't need that extra wrapper function that does nothing but call the callback. Just do:
user.save(callback);
Also, looping through the users and saving each one is much less efficient than just having mongodb do them all for you in a single command:
User.update({}, {$push: {messages: req.body.message}}, function (error) {...});
I'm trying to show defferent content for logged in and not users on one page.
Here is the code I use for generating / page:
app.get('/',function(req, res){
if (!checkSession(req, res)) {
res.render('index.ejs', {
title: 'FrontSpeak - blog-based social network'
})
} else {
res.render('index.ejs', {
title: 'autrhorized'
})
}
})
checkSession function:
function checkSession(req, res) {
if (req.session.user_id) {
db.collection('users', function (err, collection) {
collection.findOne({
_id: new ObjectID(req.session.user_id)
}, function (err, user) {
if (user) {
req.currentUser = user;
return true;
} else {
return false;
}
});
});
} else {
return false;
}
}
loggin function:
app.post('/', function(req, res){
db.collection("users", function (err, collection) {
collection.findOne({ username: req.body.username }, function (err, doc) {
if (doc && doc.password == req.body.password) {
console.log("user found");
req.session.user_id = doc._id;
}
}
});
});
});
So, it doesn't seems to be working. However, I think this is not the best way to display different content. May be there are some more elegant ways to do this? Thank you!
UPDATE: New login function:
app.post('/', function(req, res){
db.collection("users", function (err, collection) {
collection.findOne({ username: req.body.username }, function (err, doc) {
console.log('found user');
if (doc && doc.password == req.body.password) {
req.session.user_id = doc._id;
res.redirect('/');
};
res.redirect('/');
});
res.redirect('/');
});
});
This is a case of trying to apply the traditional synchronous model to Node's asynchronous callback-driven model.
After your database query completes, you return true, but you're just returning to the database driver. checkSession returned a long time ago. Since that function returns undefined if there is a session.user_id (and false if there isn't), the login check will always evaluate false.
Instead, you can use Brandon's suggestion to make checkSession asynchronous, or I recommend implementing a middleware function:
function checkLogin(req, res, next) {
if (req.session.user_id) {
db.collection('users', function (err, collection) {
if (err) return next(err); // handle errors!
collection.findOne({
_id: new ObjectID(req.session.user_id)
}, function (err, user) {
if (user) {
req.currentUser = user;
} else {
req.currentUser = null;
}
next();
});
});
} else {
req.currentUser = null;
next();
}
}
Now you have two ways of using your middleware function. If you want to check for a user on every request, just add it to the app:
app.use(checkLogin);
Now every single request will have a req.currentUser, but you incur the performance hit of fetching login state from the database for every request. Alternatively, if you only need user information for certain requests, stick the function in the route:
app.get('/', checkLogin, function(req, res) {
if (req.currentUser) {
// logged in
} else {
// not
}
});
You can read more about this in the Express docs.
It looks like you're trying to use checkSession as a synchronous function by checking its return value, but checkSession cannot be synchronous because it depends on asynchronous functionality, namely the callback here: db.collection('users', function (err, collection) .... You'll need to modify checkSession to be async:
function checkSession(req, res, callback) {
if (req.session.user_id) {
db.collection('users', function (err, collection) {
collection.findOne({
_id: new ObjectID(req.session.user_id)
}, function (err, user) {
if (user) {
req.currentUser = user;
callback(true);
} else {
callback(false);
}
});
});
} else {
callback(false);
}
}
and then use it asynchronously in your request handler:
app.get('/',function(req, res){
checkSession(req, res, function(isUser) {
if (!isUser) {
res.render('index.ejs', {
title: 'FrontSpeak - blog-based social network'
})
} else {
res.render('index.ejs', {
title: 'autrhorized'
})
}
});
})