If I've made some middleware that works together, what's the best convention for grouping and managing the functionality?
In my server.js file, I've currently just got them listed one after another with app.use calls.
It's occurred to me however that if the first one in my set doesn't produce any data, the subsequent ones in the group are fine to skip. I guess this is ultimately an aggregation although I haven't seen any examples of such in other projects.
The connect middleware has a good example for this kind of problem. Take a look at the bodyParser:
app.use(connect.bodyParser()); // use your own grouping here
is equivalent to
app.use(connect.json());
app.use(connect.urlencoded());
app.use(connect.multipart());
Internally the bodyParser function just passes the req and res objects through each of the before mentioned middleware functions
exports = module.exports = function bodyParser(options){
var _urlencoded = urlencoded(options)
, _multipart = multipart(options)
, _json = json(options);
return function bodyParser(req, res, next) {
_json(req, res, function(err){
if (err) return next(err);
_urlencoded(req, res, function(err){
if (err) return next(err);
_multipart(req, res, next);
});
});
}
};
The full code can be found at the github repo
edit
Informed in the comments bellow that passing an array will acieve the exact same thing, so no need for an additional module. :-)
I was looking for a way to do this too as my application is very granular, but I didn't want to nest everything as in the other answer.
I'm sure there is something more comprehensive out there already, but I did this in the end:
/**
* Macro method to group together middleware.
*/
function macro (...middlewares) {
// list of middlewares is passed in and a new one is returned
return (req, res, next) => {
// express objects are locked in this scope and then
// _innerMacro calls itself with access to them
let index = 0;
(function _innerMacro() {
// methods are called in order and passes itself in as next
if(index < middlewares.length){
middlewares[index++](req, res, _innerMacro)
} else {
// finally, next is called
next();
}
})();
}
}
And then use it like this:
var macro = macro(
middleware1,
middleware2,
middleware3
);
app.post('/rout', macro);
Related
I am trying to add a unique ID to my req.session but when the function executes if I want to go to another page it timeout due to infinite loading. Is there a way to do this correctly?
app.use(function (req, res, next) {
if (req.query.inv) {
sql.query(`SELECT * FROM inv WHERE inv='${req.query.inv}';`, (error, result) => {
if(error) console.log(error);
if(result.length < 1) {
req.session.inv= '';
next()
} else {
req.session.inv = `?inv=${req.query.inv}`;
console.log(req.session.inv);
next()
}
});
} else {
if(!req.session.inv) {
req.session.inv= '';
next()
}
}
});
You have a middleware which must call next() when complete so that the next middleware in the stack can be called. See Express's Using middleware documentation.
Take a look at your logic - if inv is not in your query string but does exist in your session, then next() is never called. This aligns with the issue you are having - you add inv to your session, and then on your next page load you will be forever stuck in your middleware. You would instead want logic like this:
app.use(function (req, res, next) {
if (req.query.inv) {
/* .... */
} else if (!req.session.inv) {
req.session.inv= '';
}
next(); // this makes sure that next() always gets called
});
You also have a glaring SQL Injection risk because you are taking a raw query string value and passing it directly into your query. What you want is a parameterized query - I linked to node-postgres documentation even though I'm not sure what database you are actually using. As of now your query is not safe.
I'm new to Express.
I am trying to route a request according to a value in DB. To do this I am calling a service function.
Routing is working successfully but I am loosing the request object.
I have tried to pass the req to the service but it didn't work.
Any help showing how to do this would be nice.
Here is my code block.
var companyService = require("services/companyService");
router.use('/', function (req, res, next) {
companyService.isCompanyOnline(req.body.companyCode).then(function (company) {
if (company) {
router.use('/', require("api/controllers/online"));
}
else {
router.use('/', require("api/controllers/offline"));
}
});
next();
});
module.exports = router;
Services.companyService:
function isCompanyOnline(code) {
var deferred = Q.defer();
companies.findOne({ companyCode: code }, function (err, company) {
if (err) deferred.reject(err.name + ': ' + err.message);
//if (err) throw err;
if (company) {
// return company online parameter
deferred.resolve(company.isOnline);
} else {
// company not found
deferred.resolve();
}
});
return deferred.promise;
}
You are losing the request object because you are not passing it anywhere.
I believe your main issue here is the fact that you have three route handlers registering on the same path /. If I am not mistaken they will be all called in-order that you add them. But the order in your depends on the if you are placing the router.use() calls. So it's unpredictable, and likely not going to work as you expect. Once they are registered they stay in the middleware stack until you restart the app.
I suggest you refactor to have the online/offline logic in one place, or register all your middlewares at once, so you know in which order they get called, and use next() accordingly.
On another note, if you want to pass an argument to required modules, do this:
Change api/controllers/online and the other, to accept an argument, and return the handler function that you are passing.
// your code in api/controllers/online and offline
module.exports = function (req) {
// now you have req available in-scope here
function yourFunctionThatNeedsReq (req) {
// do stuff with req
}
return yourFunctionThatNeedsReq;
};
Then update your require like so. Note the …(req).
router.use('/', require("api/controllers/online")(req) );
My solution:
First, define routes:
app.use('/api/online', require("api/controllers/online"));
app.use('/api/offline', require("api/controllers/offline"));
Then change the url in the router :
if (req.body.companyInfo.isOnline) {
req.url = '/online' + req.url + '/' + req.body.companyInfo.companyPath;
next();
}
else {
req.url = '/offline' + req.url + '/' + req.body.companyInfo.companyPath;
next();
}
I'm working on a webserver, which will load various modules for various 'web applications' on the same domain.
In the following example,
createServer(function (req, res) { // crude request routing example
var path = url.parse(req.url).pathname
if(path.match(/^\/cats(?:\/|$)/))
require('./cats.js')(req, res)
else if(path.match(/^\/dogs(?:\/|$)/))
require('./dogs.js')(req, res)
else
require('./404.js')(req, res)
})
the if statement checks for a matching context path, and routes it to the appropriate module.
Now the module cats.js looks something like this:
module.exports = function (req, res) {
var path = url.parse(req.url).pathname
// HOW can I get context path and path info? What variables should I use?
if(req.path == '/cats/add') {
//....
} else if(req.path == '/cats/delete') {
//....
} else
// 404
}
The problem is, down the road I will be updating the context path, so instead of /cats, users will have to go to /somethingelse to get to cats.js
For this reason, cats.js should not be checking for /cats/add, but should instead look for $CONTEXT/add.
What is a good way to pass the 'context path' and the 'path info' to cats.js? I'm thinking about making something up and tacking it onto the req object, but surely this is a common problem and there is a generally accepted correct solution?
You could have cat.js export a function which takes a path arg and uses it to create the request handler on the fly:
module.exports = function(context){
return function (req, res) {
var path = url.parse(req.url).pathname
if(req.path == '/'+context+'/add') {
//....
}
//....
}
}
As others mentioned, Express.JS has this capabilities built in and would be a generally accepted solution, other than that you're free to roll however you want.
I am coding a basic project manager, nothing fancy. I am writing the page where the project is created (with AngularJS) and am sending all the $scope to /create (the backend is Express.js). The router gets the JSON perfectly, and save it to a local MongoDB without problems.
My problem is that I want to set a message telling that the project was created successfully and send it back to AngularJS. This is my code.
router.js
module.exports = function(app, db) {
app.post('/create', function (req, res) {
var create = require("./../scripts/create")(req, res, db);
console.log(create); //just for testing whether I can receive the message.
});
}
create.js
module.exports = function(req, res, db) {
db.collection('projects').insert(req.body.project, function(err, docs) {
if (err) throw err;
return 'Project created.'; //I want to return this string.
});
};
I don't know how to return something from inside the db.collection.insert's callback function.
So you have to remember that anonymous function calls in JavaScript are not assigned to anywhere. They are passed, and then lost. This is usually why we don't really have return statements in them.
var x = function () { return y }; gives x the value of y but since there is never an assignment of the value of a callback, a return statement is meaningless. Callbacks, no matter if they have a return value, will not give you a value. They may feed that return value up to the function that they were given to, but they are entirely lost to you.
The way to get around this is to do some trickery with the scope. Basically what you want to do is 'bump' the value you want to return up a scope you can assign and then return it there. For example, you can do this:
module.exports = function(req, res, db) {
var stringToReturn;
db.collection('projects').insert(req.body.project, function(err, docs) {
if (err) throw err;
stringToReturn = 'Project created.'; //I want to return this string.
});
return stringToReturn;
};
This will work because the return value gets bound to module.exports, which is in turn bound to the result of
var create = require('./create');
console.log(create('something')) //should log 'Project created.'
Solved!!
Router.js
module.exports = function(app, db) {
app.post('/create', function(req, res) {
var create = require("./../scripts/create")(req, res, db);
});
});
Create.js
module.exports = function(req, res, db) {
db.collection('projects').insert(req.body.project, function(err, records) {
if (err) throw err;
res.send("Project created.");
});
};
Now Angular is receiving the response from the server.
I am trying to developpe an API with NodeJs which accepts an object containing multiple queries to mongdb and answers an object with the different results (in fact Json).
I use express and my code is :
var nb_query=0;
var results;
//api
app.get("/api/:p",api);
function api(req, res) {
var jsonq=decodeURIComponent(req.params.p);
//console.log(jsonq);
var queries=JSON.parse(jsonq);
nb_query=Object.keys(queries).length;
results={};
for(var nq in queries) { // for each query
do_find_query(nq,queries[nq], function() {
//todo : managing head
res.end(JSON.stringify(results));
}
);
}
} // end of api function
function do_find_query (name_query,query,callback) {
var collection=fdb.collection(query.collection);
collection.find(query.find,query.fields,query.options).toArray(function(err,docs) {
if(err) throw err;
results[name_query]=docs;
nb_query--;
if(nb_query==0)
callback();
}
);
}
As you see, I use global vars to store the results and the counter nb_query. And I ask myself if it is a problem or not (now no because I am alone on the server, but when we will be thousands of billions? :-) ).
As I understand Node, there is only one thread and I think Node will finalize a started job unless he encoutered an io access. In this case, he stacks the io with the callback, and begins to answer to a new request.
If this is correct, I think that Node could answer to 2 or more different calls to my api (which need mongo calls) and so store different values in global vars which is shared (there's only one thread).
If this is right, I would also know what is the best way to change it.
I have the idea of declaring results and nb_query in api function and pass it to do_find_query, but nb_query isn't an object and is so not changed correctly.
I know I can put nb_query in an object to pass it 'by reference', but I want to know first if it is necessary and if it is a good way or if there is a better one.
Thanks for your help !
Doom.
------------------------------------------------------------------------------
EDIT :
I have change my code and it seems to work without global vars and without async library (which is for me using a hammer to swat a fly)
//api
app.get("/api/:p",api);
function api(req, res) {
var jsonq=decodeURIComponent(req.params.p);
//console.log(jsonq);
var queries=JSON.parse(jsonq);
var query_names=Object.keys(queries);
var results={};
var query_left=query_names.length;
query_names.map( function(query_name) {
var query=queries[query_name];
var collection=fdb.collection(query.collection);
collection.find(query.find,query.fields,query.options).toArray(function(err,docs) {
if(err) throw err; //todo : handle errors in a better way
results[query_name]=docs;
if(--query_left==0)
res.json(results);
}
);
}
);
}
But I still do not know if this is necessary to do or not. (I think so but I am new in Node so ...)
Thanks to mscdex as his answer make me known res.json() and help me understand scope of variable.
Instead of using globals, try this (uses the async module):
var async = require('async');
// ...
app.get('/api/:p', api);
function api(req, res) {
var jsonq = decodeURIComponent(req.params.p),
queries = JSON.parse(jsonq),
keys = Object.keys(queries),
queriesLeft = keys.length,
results = {};
async.each(keys, function(name, cb) {
var query = queries[name],
collection = fdb.collection(query.collection);
collection.find(query.find, query.fields, query.options)
.toArray(function(err, docs) {
if (err) return cb(err);
results[name] = docs;
cb();
}
);
}, function(err) {
if (err) throw err; // TODO: handle better
res.json(results);
});
} // end of api function