How can I use express router routes for dual use? - node.js

So I have a node app using express router and routes, for example I have a router that handles fetching company objects from the database, within the Company router the get method looks like this:
router.get('/:id', function(req, res, next) {
var id = req.params.id;
models.Company.get(Number(id), function(err, entity) {
if (err) {
console.error("Get error:", err);
}
console.log('Got entity:', entity);
req.data = entity;
next()
});
});
Now if I am in another router, say the Reviews router and I want to get a company I would like to be able to call this route in order to grab that company, instead of having to duplicate code.
How do I do that?

You can either use a named function, if you really want to use the same handler:
function yourHandler(req, res, next) {
var id = req.params.id;
models.Company.get(Number(id), function(err, entity) {
if (err) {
console.error("Get error:", err);
}
console.log('Got entity:', entity);
req.data = entity;
next()
});
}
router.get('/:id', yourHandler);
router2.get('/companies/:id', yourHandler);
Or you can abstract away only the actual database lookup, by making a function that takes a callback:
function getCompany(id, callback) {
models.Company.get(Number(id), function(err, entity) {
if (err) {
console.error("Get error:", err);
callback(err);
} else {
console.log('Got entity:', entity);
callback(null, entity);
}
});
}
or a function that returns a promise:
function getCompany(id) {
return new Promise(function (resolve, reject) {
models.Company.get(Number(id), function(err, entity) {
if (err) {
console.error("Get error:", err);
reject(err);
} else {
console.log('Got entity:', entity);
resolve(entity);
}
});
});
}
The difference is that you use the callback-taking version like this:
getCompany(123, function (err, data) {
if (err) {
// handle error
} else {
// you have data
}
});
and you use the promise-returning version like this:
getCompany(123)
.then(function (data) {
// you have data
})
.catch(function (err) {
// handle error
});
The callback versions are easy to compose using the async module and the promise versions are easy to compose using the bluebird module or some other promise framework. See:
http://caolan.github.io/async/
http://bluebirdjs.com/
It's also worth mentioning that Bluebird is much faster than native ES6 promises, see this answer for details.

Related

How to reuse database controllers

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
};

Why my nodejs requests are slow?

Im new in nodejs, and Im trying to learn by creating an app that has a list of users, that I can add and remove those users. Im using angularjs in frontend to send request to nodejs and after that to mongodb. The problem is that, if I click a lot of times in the button "adduser" a lot of times, my app goes slow.
To interact to mongodb I use:
app.get('/users',function (req, res) {
mongoose.model('Usuario').find(function (err, list) {
res.send(list);
});
});
app.post('/addusuario', function (req,res) {
var usuario = new Usuario(req.body);
usuario.save(function (err) {
if (err) {
console.log(err);
} else {
console.log('Usuario salvo com sucesso');
}
}); });
app.delete('/delusuario/:id', function (req, res) {
var id = req.params.id;
mongoose.model('Usuario').findByIdAndRemove(id , function(err) {
if(err) {
console.log(err);
} else {
console.log('Usuario removido com sucesso!');
}
});
});
Im my angularapp:
app.controller('AppCtrl', function($scope, $http, Data) {
function reload() {
Data.get('users').then(function(data){
$scope.usuarios = data;
console.log(data);
});
};
$scope.addUsuario = function(usuario) {
Data.post('/addusuario', usuario);
reload();
};
$scope.deletarUsuario = function(id) {
Data.delete("/delusuario/"+id).then(function(result) {
});
reload();
};
reload();
});
I dont know why it is becaming slow after I click to add user more than 3 times..
What I see in your code that you are not sending an response back to the user, you should do something after insert or delete in the database. res.end();
You should rewrite your code in the following way:
app.get('/users',function (req, res) {
mongoose.model('Usuario').find(function (err, list) {
res.send(list);
});
});
app.post('/addusuario', function (req,res) {
var usuario = new Usuario(req.body);
usuario.save(function (err) {
if (err) {
console.log(err);
res.json({err: err});
} else {
res.json({ok: true});
console.log('Usuario salvo com sucesso');
}
}); });
app.delete('/delusuario/:id', function (req, res) {
var id = req.params.id;
mongoose.model('Usuario').findByIdAndRemove(id , function(err) {
if(err) {
console.log(err);
res.json({err: err});
} else {
res.json({ok: true});
console.log('Usuario removido com sucesso!');
}
});
});
You block the stack by not returning the response to the client. And this is most probably the cause of your slow request.

ExpressJS Multiple middlewares connected to callbacks

I have an ExpressJS app which takes form data and does the following:
1. checks all required values are supplied,
2. validates the data is valid,
3. adds a record to the database to get a unique ID,
4. uses the ID and data to call a separate server,
5. upon response from the server, update the database record with details of the response.
I'm using mongoskin for the database.
My question relates to how I control the flow. Essentially I have written each of the above steps as a middleware function because I need to call next() on the success (or next(err) on error) at each callback.
It seems like I'm writing too much middleware and should be able to group the steps into larger sets of middleware containing multiple 'sub-functions' but I'm not sure how to do this in Express since I need to call next() every time an async function call completes. Is there a correct way to do this or is this 'one middleware per step' approach really the right way to run this?
EDIT: Posting some code as requested. This is partial code for the sake of brevity:
function validateFields(req, res, next) {
//...
//iterate over req.body to confirm all fields provided
//...
if (allDataProvided) {
//...
//iterate over req.body to confirm all fields valid
//...
if (allDataValid) {
return(next());
} else {
return(next(err));
}
} else {
return(next(err));
}
},
//get an auto incrementing ID fields from a mongodb collection (counters)
function getNextID(req, res, next) {
counters.findAndModify(
{ _id: "receiptid" },
[['_id','asc']],
{ $inc: { seq: 1 } },
{},
function(err, doc) {
if (err) {
return next(err);
} else {
req.receiptid = doc.seq;
return next();
}
});
},
//insert a new record into the transaction collection (txns) using the new ID
function createTransaction(req, res, next) {
txns.insert(
{ _id : req.receiptid,
body : req.body,
status : "pending"},
{},
function(err, r) {
if (err) {
return next(err);
} else {
return next();
}
});
},
//process the data on the remote web service using the provider's API (remoteapi)
function processTransaction(req, res, next) {
remoteapi.processTransaction(
{ data: req.body,
receiptid: req.receiptid },
function(err, r) {
if (err) {
return next(err);
} else {
req.txnReceipt = r;
return next();
}
});
},
//update the record in the database collection (txns) with the server response
function updateDatabase(req, res, next) {
txns.updateById(req.receiptid,
{ $set :{status : "success",
receipt: req.txnReceipt }
}, function (err, r) {
if (err) {
return next(err);
} else {
return next();
}
});
}
And as it currently stands with the above functions, my route which utilises this middleware starts like this:
router.post('/doTransaction',
validateFields,
getNextID,
createTransaction,
processTransaction,
updateDatabase,
function(req, res, next) { //...
It seems like I should be able to create one middleware function which does all of these things in a row without each having to be a separate middleware, but since each middleware has an async function in it and I need to call next() in the resulting callback, this is the only way I can see it working.
Thanks
Aaron
It's fairly easy to implement all your steps in one middleware. I've included some pseudo-code below (that makes various assumptions on how your code is structured, because you didn't provide implementation details, but it's just to give an idea).
It uses the on-headers package to "catch" responses.
var onHeaders = require('on-headers')
// Your middleware function
app.use(function(req, res, next) {
// Update the database when the response is being sent back.
onHeaders(res, function() {
// Do database update if we have a document id.
if (req._newDocumentId) {
db.collection.update(req._newDocumentId, data, function() {
// can't do a lot here!
});
}
});
// Perform the requires steps
if (! checkValuesAreSupplied(req)) {
return next(new Error(...));
}
if (! validateValues(req)) {
return next(new Error(...));
}
// Insert into database.
db.collection.insert(data, function(err, doc) {
if (err) return next(err);
...process the newly created doc...
// Store _id in the request for later.
req._newDocumentId = doc._id;
// Make the call to the separate server
makeCallToOtherServer(otherData, function(err, response) {
if (err) return next(err);
...process response...
return next();
});
});
});
You can put everything in one module and just use callbacks to go trought each step but in this case you can get "callback hell".
So I can suggest the async npm package which I think the better way.
using this library your code will look like:
function allInOneMiddleware(req, res, next) {
async.waterfall([
function (callback) {
validateFields(req, res, callback);
},
getNextID,
createTransaction,
processTransaction,
updateDatabase
], function (err) {
if (err) {
return next(err);
}
// response?
});
}
function validateFields(req, res, callback) {
//...
//iterate over req.body to confirm all fields provided
//...
if (allDataProvided) {
//...
//iterate over req.body to confirm all fields valid
//...
if (allDataValid) {
return callback(null, req.body);
}
return callback(err);
}
return callback(err);
}
//get an auto incrementing ID fields from a mongodb collection (counters)
function getNextID(body, callback) {
counters.findAndModify(
{_id: "receiptid"},
[['_id', 'asc']],
{$inc: {seq: 1}},
{},
function (err, doc) {
if (err) {
return callback(err);
}
callback(null, body, doc.seq);
});
}
//insert a new record into the transaction collection (txns) using the new ID
function createTransaction(body, receiptid, callback) {
txns.insert(
{
_id: receiptid,
body: body,
status: "pending"
},
{},
function (err, r) {
if (err) {
return callback(err);
}
callback(null, body, receiptid);
});
}
//process the data on the remote web service using the provider's API (remoteapi)
function processTransaction(body, receiptid, callback) {
remoteapi.processTransaction(
{
data: body,
receiptid: receiptid
},
function (err, r) {
if (err) {
return callback(err);
}
callback(null, receiptid, r);
});
}
//update the record in the database collection (txns) with the server response
function updateDatabase(receiptid, txnReceipt, callback) {
txns.updateById(receiptid,
{
$set: {
status: "success",
receipt: txnReceipt
}
}, callback);
}
Thanks Nicolai and robertklep for the answers. Whilst I think both answers do answer the question, I realised as I was working through this myself that I had failed to see the forest for the trees.
I could just pass the next function through each callback function until I reached the final one and call it to pass the control back to the middleware stack. This also allows me to simply call next(err) inside any of those functions.
So my answer is very similar to the concept outlined by Nicolai except I don't think I need to use the async package in this case because I don't feel like this particular case took me to callback hell.
Here is my answer to my own question:
function validateFields(req, res, next) {
//...
//iterate over req.body to confirm all fields provided
//...
if (allDataProvided) {
//...
//iterate over req.body to confirm all fields valid
//...
if (allDataValid) {
getNextID(req, res, next)
} else {
return(next(err));
}
} else {
return(next(err));
}
},
//get an auto incrementing ID fields from a mongodb collection (counters)
function getNextID(req, res, next) {
counters.findAndModify(
{ _id: "receiptid" },
[['_id','asc']],
{ $inc: { seq: 1 } },
{},
function(err, doc) {
if (err) {
return next(err);
} else {
req.receiptid = doc.seq;
createTransaction(req, res, next);
}
});
},
//insert a new record into the transaction collection (txns) using the new ID
function createTransaction(req, res, next) {
txns.insert(
{ _id : req.receiptid,
body : req.body,
status : "pending"},
{},
function(err, r) {
if (err) {
return next(err);
} else {
processTransaction(req, res, next);
}
});
},
//process the data on the remote web service using the provider's API (remoteapi)
function processTransaction(req, res, next) {
remoteapi.processTransaction(
{ data: req.body,
receiptid: req.receiptid },
function(err, r) {
if (err) {
return next(err);
} else {
req.txnReceipt = r;
updateDatabase(req, res, next);
}
});
},
//update the record in the database collection (txns) with the server response
function updateDatabase(req, res, next) {
txns.updateById(req.receiptid,
{ $set :{status : "success",
receipt: req.txnReceipt }
}, function (err, r) {
if (err) {
return next(err);
} else {
return next();
}
});
}
So instead of calling next() on successful completion of each async function and having to write another middleware for the next step, I simply pass next on to the next function until it's required.
This was, I can just call the first function as my middleware, like this:
router.post('/doTransaction',
validateFields,
function(req, res, next) { //...
and in turn, the remaining steps are called in sequence when each action completes.

PUT and DELETE always route to GET in Node + Express

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.

Custom callback in Express.js get

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

Resources