Express middleware with async operation that happens only once - node.js

I am trying to append some data to my request object using a middleware, but I want to do it only once the server is up.
So I tried doing it with a middleware, while trying to use a function's context, but it's a bit problematic to perform such an action on a middleware, because I cannot pass a promise as a middleware.
This is what I'm trying to do:
const setupData = async () => {
const data = await getSomeData();
return (req, res, next) => {
req.data = data;
next();
}
}
app.use(setupData());
I tried using the solution suggested here, but it won't work as this will happen on every request.
Any idea how can I go around this? I can always put the info on a global var, but I would like to avoid it.
I also saw some in-memory packages to help with it (such as node-cache), but I would like to do it with a middleware.
Thanks in advance

Just cache the result using a normal variable:
let data = null;
function setupData (req, res, next) {
if (data !== null) {
req.data = data;
next();
}
else {
getSomeData().then(result => {
data = result
req.data = data;
next();
});
}
}
app.use(setupData);
This is the minimal, least complicated implementation. You can of course refactor it to be much DRYer and less prone to mistakes by taking out the caching logic:
Cleaner Implementation
let cache = null;
async function getCachedData() {
if (cache === null) {
cache = await getSomeData();
}
return cache;
}
Which makes setupData much cleaner:
function setupData (req, res, next) {
getCachedData().then(data => {
req.data = data;
next();
});
}
Either way, the cache is triggered on the first request. This means that there is a possibility that a second request may arrive before the data is possibly cached. So at startup the getSomeData() function may run more than once.
Really call getSomeData() ONLY ONCE
If you really want to call getSomeData only once you must call it before setting up Express:
async function main () {
const data = await getSomeData();
const app = express();
//
// set up express middlewares...
//
app.use((req,res,next) => {
req.data = data;
next();
});
//
// set up routes...
//
app.listen(config.port);
}
main(); // start everything
The key here is to realize that we have been trying to do everything backwards: to set up a constant value asynchronously AFTER starting to set up Express. The natural flow of the program wants the constant value to exist BEFORE we begin setting up Express so we only perform everything else inside the async function (here called main). Not try to run the async function while setting up Express.

You can do it without async:-
const setupData = (req, res, next) => {
// You can put a condition here so that it runs only once
getSomeData().then((data) => {
req.app.locals.data = data //The data can be accessed in the next middleware using req.app.locals
next();
}).catch((error) => {
console.log("Error Occured");
res.status(400).end("Error Occurred");
})
}
app.use(setupData);
You should see the documentation of getSomeData and see how it works

Related

Jest Express testing middleware with arguments

I'm pretty new to node and this is my first time unit testing an app. I'm doing well with Jest faking the request with Jest function as below
// Create a fake request
const mockRequest = (sessionData, body) => ({
session: { data: sessionData },
body
});
// Create a fake response
const mockResponse = () => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
return res;
};
const mockNext = () => {
const next = jest.fn();
return next;
};
So I can use them like follows
doSomething(req, res, next);
expect(res.status).toHaveBeenCalledWith(201);
//or
expect(next).toHaveBeenCalled();
That's enough for all the cases until I found that my authorisation middleware includes a couple of parameters so I can not pass the fake res and req as below
exports.isAllowedTo = (par1, par2) => {
return async (req, res, next) => {
try {
//
// Grant logic here that needs par1 and par2
//
if(granted)
next();
else
return res.status(401).json({
error: "You don't have enough permission to perform this action"
});
} catch (err) {
res.status(406).json({
error: err.toString(),
})
}
}
}
If I test isAllowTo(req, res, next) with the mock req, res and next then I'm missing the 2 parameters needed by the function. Actually when I do this, the function isAllowTo() is not even called. I don't know how to deal with that. Any suggestion or approach?
Two months later I realized that the real problem is that I'm testing a function inside of another function.
So firstly I store the function in a variable so I can test it as a regular middleware.
test('Grant access if user role is allowed to', async () => {
const isAllowToTester = userController.isAllowedTo(par1, par2);
await isAllowToTester(req, res, next)
expect(next).toHaveBeenCalled();
});
Hope this helps someone else.
Credits to this post
Check out https://github.com/nock/nock it's a library dedicated to mocking requests and responses, it's really easy to use with unit tests/jest. I personally don't think is worth it to write your own mocking implementation.

Return res and next in a custom express function

How do I return both res and next in an express function:
const customfunction = async (req, res, next) => {
try {
// how do I set cookie and return next()?
return res.cookie('someToken', someToken, {
signed: true,
// etc...
}
);
return next();
} catch (err) {
// catch here, for example, return res.status(401).clearCookie...
}
}
An express request handler (like something you pass to app.get() or router.post() or something like that) does not pay any attention to the return value from that handler.
So, return inside such a handler is used only for flow control to stop further execution of the function.
In addition, your code has two return statements one after the other:
return res.cookie(...);
return next();
Which makes no sense because the return next() line of code will never be executed as the function has already returned on the line before.
If this is middleware and you intend for some other request handler to still have a chance to process this request, then you would want something like this:
const customfunction = async (req, res, next) => {
res.cookie('someToken', someToken);
next();
};
It doesn't appear that there's any reason for the try/catch because neither of these should throw an exception (assuming your code doesn't have a syntax error in it).
But, if you really wanted the try/catch, you could do this:
const customfunction = async (req, res, next) => {
try {
res.cookie('someToken', someToken);
next();
} catch(e) {
// make sure logs can see that this unexpected error is happening
console.log("customFunction error", e);
res.clearCookie('someToken');
res.status(500).send("Internal Error"); // probably want a more glamorous error page
}
};

Express.js - Get the res.send value

My question is exactly this one. The problem is that when I apply the solution to that question, nothing happens - the value of res.send isn't logged. I use express 4.16.4.
Here's my code:
/* FILE: /app.js: */
...
const postRoute = require('./middleware/postRoute');
const myRoute = require('./routes/myRoute');
...
app.use('/', myRoute);
app.use(postRoute);
...
/* FILE: /routes/myRoute */
...
router.post('/myRoute', [], (req, res, next) => {
res.send({ status:'success', message:'Test success. Feels good, man.' });
next();
});
...
/* FILE: /middleware/postRoute */
const postRoute = function(req, res, next) {
console.log('postRoute: ');
var send = res.send;
res.send = function(chunk, encoding){
res.send = send;
if (chunk) {
console.log('postRoute chunk: ', chunk); // This statement is never reached
}
res.send(chunk, encoding);
};
next();
};
module.exports = { postRoute };
When I make a POST request to /myRoute using Postman, it logs the following: postRoute:, and that's it. The second console.log() is never reached and presumably neither is any other statement in the same function() as the console.log().
The question I link to at the start of my question was from 2015, when I assume they used a different version of Express.js, and that's why their solution isn't working for me? In my middleware code example, I used the code from a question that was linked in one of that question's answers, because it's solution didn't work for me. But that solution's obviously also not working, otherwise I wouldn't be here - and it's from 2012! Who even knows what version of Express they used back then!?
So to reiterate and conclude my question: How do I use middleware to log the value passed to res.send()?
Follow up question (but let me know if I should rather ask this in a separate question): Is there a way to call middleware after the route/response from the route instead of globally. So router.post('/myRoute', [postRoute], (req, res, next) => {...}); instead of app.use(postRoute)?
Here's what I ended up doing. I kept my file structure exactly the same, but instead of res.send()ing from the route, I attached the object that I would have sent, to the res object and then call next() at the end of my route. So for instance, res.send({message:'blah'}); becomes res.return = {message:'blah'}; next();. I use .return because I don't believe such a property exists on the res object by default and I find it descriptive enough for my purposes. Then in my postRoute middleware, I can easily access res.return, after which I call return res.send(res.return).
Using my example from the question (app.js stays exactly as you see it):
/* FILE: /routes/myRoute */
...
router.post('/myRoute', [], (req, res, next) => {
if(theConditionIsMet()) {
res.return = { message:'The condition was met.' };
} else {
res.return = { message:'The condition was not met.' };
}
next();
});
....
router.post('/anotherRoute', [], (req, res, next) => {
// In this route I don't need to intercept the response, so I just do it like normal.
return res.send({ message:'Feels good, man.' });
});
...
/* FILE: /middleware/postRoute */
const postRoute = function(req, res, next) {
if(res.hasOwnProperty('return') {
return res.send(res.return);
}
};
module.exports = { postRoute };
One flaw I can already see is that I'll now have to restructure all my routes to use this method of res.send()ing (or at least all of the routes for which I want to intercept the return value). Because of this, and because I imagine someone more knowledgeable than me could probably figure out a better way to do it, I'm not going to accept my own answer.

How should I pass three arguments to an express/node arrow function?

I am declaring my function like this:
const parseConnections = (connectionsCSVPath, req, res) => {
//do a bunch of stuff
}
Inside the function, if I try to call res.locals.something, I get an error saying "cannot read property locals of undefined" I have tried several other syntaxes, such as this:
const parseConnections = ((connectionsCSVPath, req, res) => {
//do a bunch of stuff
})
this:
const parseConnections = (connectionsCSVPath, (req, res) => {
//do a bunch of stuff
})
and this:
const parseConnections = connectionsCSVPath, (req, res) => {
//do a bunch of stuff
}
and they all throw errors. What is the proper way to pass these 3 arguments to the function so that all 3 are defined inside?
Edit*: The function is then called like this:
router.post(
'/upload/engagements/batch', checkIfAuthenticated,
parseConnections('./tmp/connections.csv'),
parseMessages('./tmp/messages.csv'), (req, res) => {
//do a bunch of stuff
}
The problem is not (necessarily) with how you define the function but with how you are using it.
parseConnections('./tmp/connections.csv') calls the function right then and there. You are only passing a single argument to it, so req and res will be undefined.
function foo(a, b, c) {
console.log('a:', a);
console.log('b:', b);
console.log('c:', c);
}
foo('first argument');
However, you cannot pass values for req and res because these values are created and passed by express itself.
Essentially you are making the mistake of calling a function where you should be passing it. router.post expects to be passed one or more functions. But you are calling parseConnections and pass its return value instead which is probably undefined.
Here is a simple example that demonstrates the difference:
function foo(x) {
console.log('inside foo', 'x is ', x);
}
// bar expects to be passed a function that it can call
function bar(callback) {
console.log('bar received:', callback);
try {
callback(42);
} catch(e) {
console.error(e);
}
}
// this will work as expected
console.log('Passing a function');
bar(foo);
// this will error because `bar` doesn't receive a function.
// this is what you are doing
console.log('Calling a function and passing its return value');
bar(foo(21));
One way to fix your problem is to make parseConnections return a function, which is then received by router.post. I'm using normal function declarations here so that the syntax is not too confusing:
function parseConnections(connectionsCSVPath) {
return function(req, res) {
//do a bunch of stuff
};
}
This requires no changes to your router.post call.
Another solution is to pass a function to router.post that calls parseConnections instead, passing along req and res:
router.post(
'/upload/engagements/batch',
checkIfAuthenticated,
(req, res) => parseConnections('./tmp/connections.csv', req, res),
// alternatively you can use `.bind`:
// parseConnections.bind(null, './tmp/connections.csv'),
parseMessages('./tmp/messages.csv'), // <- this is likely wrong as well,
// but I leave this to you to figure out
(req, res) => {
//do a bunch of stuff
}
);

Node.js trouble with callback in loop

Being new to Node, I am still having some troubles with callbacks.
In the mapBpiIfindex function I am trying to loop through all of the VLANs found by the vlans function. Once it has looped through all VLANs, creating the map, I want to output the map to the browser. But, the only output I am getting is {}. How can I send the mapping to the browser? I am not even sure if I am using my callbacks correctly.
var express = require('express');
var router = express.Router();
var snmp = require('snmp-native');
// Create a Session with explicit default host, port, and community.
let session = new snmp.Session({ host: 'AASW0120', port: 161, community: 'community' })
let Mibs = {
hostname: [1,3,6,1,2,1,1,5,0],
vlans: [1,3,6,1,4,1,9,9,46,1,3,1,1,2],
dot1dBasePortIfIndex: [1,3,6,1,2,1,17,1,4,1,2]
}
/* Get all VLANs on switch */
function vlans(snmpSession, cb) {
let vlans = []
session.getSubtree({ oid: Mibs.vlans }, function (error, varbinds) {
if (error) {
console.log('Fail :(');
} else {
varbinds.forEach(function (varbind) {
vlans.push(varbind.oid[varbind.oid.length -1])
})
}
cb(vlans)
})
}
/* Map BPIs to Ifindices */
function mapBpiIfindex(session, cb) {
let map = {}
vlans(session, function (vlans) {
vlans.forEach(function (vlan) {
session.getSubtree({oid: Mibs.dot1dBasePortIfIndex, community: 'community#' + vlan}, function (error, varbinds) {
if (error) {
console.log('Fail :(')
} else {
varbinds.forEach(function (varbind) {
map[varbind.oid[varbind.oid.length -1]] = {ifindex: varbind.value, vlan: vlan}
})
}
})
})
cb(map)
})
}
router.get('/vlans', function (req, res, next) {
vlans(session, function (vlans) {
res.send(vlans)
})
})
router.get('/bpi-ifindex', function (req, res, next) {
mapBpiIfindex(session, function (mapping) {
res.send(mapping)
})
})
The answer is no, youre not using it correctly ;)
A few things here:
You should be clear that only the code within the callback is executed after the operation has finished, so cb(map)does not wait until all youre looped callbacks have finished. Thats why nothing is returned (because when cb is called, the async functions have not finished yet and map values are undefined. Have a look at this How do I return the response from an asynchronous call?, its the same principle.
Have a look at async module. Specifically, the do* or whilst methods. It'll help you process loops with async function calls.
Apart from that, you should not use forEach if you mind about performance.

Resources