sending a streaming response to a REST query with async operation - node.js

update: clarified and edited code to reflect what I really want, that is, send a streaming response, that is, send back matched results as they arrive from their own async matching process.
consider (using expressjs-ish code)
app.post('/', jsonParser, function (req, res) {
if (!req.body) return res.sendStatus(400)
// matches is not needed for this scenario so
// commenting it out
// var matches = [];
req.body.forEach(function(element, index) {
foo.match(
element,
function callback(error, result) {
if (error) {
console.log(error); // on error
}
else {
⇒ if (some condition) {
// matches.push(result);
⇒ res.send(result);
}
}
}
);
});
// moved this above, inside the callback
// ⇒ res.send(matches);
});
The input to post('/') is an array of terms. Each term is matched using foo which has a callback after every call. I want to send back all the matches that satisfy "some condition" (see ⇒ in the code above). Ideally, it would be good to send back a streaming response, that is, send a response back as the matches occur (because foo.match() might take a while for each term). How do I go about this?

Does something like that works for you? I' ve used the stream-array module. Possibly this can be helpful for you? How to emit/pipe array values as a readable stream in node.js?
var streamify = require('stream-array');
app.post('/', jsonParser, function (req, res) {
if (!req.body) {
return res.sendStatus(400);
}
var matches = [];
req.body.forEach(function (element, index) {
foo.match(
element,
function callback(error, result) {
if (error) {
console.log(error); // on error
} else {
if (some condition) {
streamify([result]).pipe(res);
}
}
}
);
});
// res.json(req.body);
});

Related

TypeError: validator.escape is not a function - (express-validator#6.12.1 package)

Codecademy video: link
Explanation:
As part of my Codecademy Back-End Engineer training, I have to do a project outside of their platform. The goal of this project is to make sure a node application is protected from common web attacks.
One challenge I faced was securing the code from Cross-Site Scripting (XSS) attacks. To do this, I used a package called express-validator#6.12.1. The code uses a function called validator.escape which is supposed to protect against any malicious code being inserted into an input form. However, I am getting an error in the console when I try to use it.
Terminal output :
TypeError: validator.escape is not a function
Here is the code :
const validator = require("express-validator");
app.post("/public_forum", function (request, response) {
if (request.session.loggedin) {
var comment = validator.escape(request.body.comment);
var username = request.session.username;
if (comment) {
db.all(
`INSERT INTO public_forum (username,message) VALUES ('${username}','${comment}')`,
(err, rows) => {
console.log(err);
}
);
db.all(`SELECT username,message FROM public_forum`, (err, rows) => {
console.log(rows);
console.log(err);
response.render("forum", { rows });
});
} else {
db.all(`SELECT username,message FROM public_forum`, (err, rows) => {
console.log(rows);
console.log(err);
response.render("forum", { rows });
});
}
comment = "";
} else {
response.redirect("/");
}
comment = "";
//response.end();
});
In the video of Codecademy, the guy uses this function.
Try with:
const {check, validationResult} = require('express-validator');
app.post('/public_forum', async function (request, response) {
if (request.session.loggedin) {
await check('comment').trim().escape().run(req);
const validationResult = await validationResult(req);
if (validationResult.isEmpty()) {
// Good to go...
const { comment } = req.body;
}
...
Link to official docs
I have implemented your code. I tried to add both a malicious and safe comment, but I got an error message on my browser that said, "Port 4000 Not Found." Every time I run the code, it kills the port. So I have implemented another code that works well based on what you sent me.
// This code defines a post request handler for the "/public_forum" endpoint.
app.post('/public_forum', async function (request, response) {
// Check if the user is logged in by checking the session data.
if (request.session.loggedin) {
// Trim and escape the incoming comment.
await check('comment').trim().escape().run(request);
// Get the validation result of the incoming comment.
const errors = validationResult(request);
// If the validation result contains errors, return a 400 status with the errors in a JSON format.
if (!errors.isEmpty()) {
return response.status(400).json({ errors: errors.array() });
}
// Get the comment from the request body.
const { comment } = request.body;
// If a valid comment exists, insert it into the "public_forum" database table.
if (comment) {
db.run(
`INSERT INTO public_forum (username,message) VALUES (?,?)`, [request.session.username, comment],
(err) => {
// If an error occurs while inserting the comment, log the error.
if (err) {
console.error(err);
}
}
);
}
// Select all the rows from the "public_forum" table.
db.all(`SELECT username,message FROM public_forum`, (err, rows) => {
// If an error occurs while selecting the rows, log the error.
if (err) {
console.error(err);
}
// Log the selected rows.
console.log(rows);
// Render the "forum" template, passing in the selected rows as a parameter.
response.render("forum", { rows });
});
} else {
// If the user is not logged in, redirect them to the homepage.
response.redirect("/");
}
});

What is next() doing in this Express route and why is its order in the code important?

I'm getting confused with next(); I read through this post which essentially says it passes control to the next route. In my code below, having next(); where it is causes me to get "Cannot set headers after they are sent to the client". However, if I comment that out and then restore the else clause of my if statement, it functions correctly both when an incorrect empID is passed as well as when a correct one is. I'm hoping someone could explain what exactly is happening? Why does the position of the next() matter? It seems like it would be called either way?
I'm trying to do what is happening in this post which is add a value to, say req.user, but I haven't been able to get that to work at all so I'm trying the method I have here.
let checkEmp = (req, res, next) => {
db.get("select * from Employee where id = $id", {$id: req.empID},
(err, row) => {
if (err || row === undefined) {
res.status(404).send();
// } else {
// next();
}
});
next();
};
// get all timesheets
timesheetRouter.get("/", getParams, checkEmp, (req, res, next) => {
if (req.empID) {
db.all("select * from Timesheet where employee_id = $id", {$id: req.empID},
(err, rows) => {
if (err) {
next(err);
} else {
return res.status(200).send({timesheets: rows});
}
});
} else {
return res.status(404).send("Nothing to see here");
}
});
Looks like db.get() is probably asynchronous, so in the example as shown, next() will be called before db.get() finishes and it moves on to the next handler. Then, when the db.get() finishes, it tries to send a response, but the response has already been sent by the anonymous function in the main handler. By moving the next() inside of db.get(), you're essentially waiting for it to finish before moving on.

Sails blueprints lifecycle

I need to add some additional data to result of find blueprint. I found this solution:
module.exports = {
find: function(req, res) {
return sails.hooks.blueprints.middleware.find(req, res);
}
}
but I can`t find any way to change response here, or add callback into the blueprint. I even try to change blueprint and add the cb in it:
module.exports = function findRecords (req, res, cb) {
...
if (typeof cb === 'function') res.ok(cb(result));
else res.ok(result);
but in this case it returns 500 statusCode every time (but with corresponding data)
I have been struggling with the same issue for a couple of time. Here is my hack (with explanation) to solve this.
The build in blueprint will always make a call to res.ok, res.notFound, or res.serverError if an error occurs. With altering of this method calls, it is possible to modify the output.
/**
* Lets expose our own variant of `find` in one of my controllers
* (Code below has been inserted into each controller where this behaviour is needed..)
*/
module.exports.find = function (req, res) {
const override = {};
override.serverError = res.serverError;
override.notFound = res.notFound;
override.ok = function (data) {
console.log('overriding default sails.ok() response.');
console.log('Here is our data', data);
if (Array.isArray(data)) {
// Normally an array is fetched from the blueprint routes
async.map(data, function(record, cb){
// do whatever you would like to each record
record.foo = 'bar';
return cb(null, record);
}, function(err, result){
if (err) return res.error(err);
return res.ok(result);
});
}
else if (data){
// blueprint `find/:id` will only return one record (not an array)
data.foo = 'bar';
return res.ok(data);
}
else {
// Oh no - no results!
return res.notFound();
}
};
return sails.hooks.blueprints.middleware.find(req, override);
};
Seems like only copy-paste solution existed. So I copy all code from files in node_modules/sails/lib/hooks/blueprints/actions to the actions of every controller and then change it.

New to NodeJS. Mongoose How to Async multiple queries?

Ok, lets say I have two Models. Contract and CommLog. Both work find independently but I need many CommLog to relate to each Contract.
In the ContractSchema trying async
ContractSchema.methods.getCommLog = function getCommLog(){
var log = false;
async.parallel([
function(){
CommLog.find({commType:'contract',parent:this._id},function(err,comms){
log = comms;
});
}],
function(){return log;});
};
Where I am trying to use it
router.get('/:code', function(req, res, next) {
Contract.findOne({accessCode:req.params.code},function(err,contract){
if(err)
res.send(err);
var data;
if(contract != null){
var comms = contract.getCommLog();
data = {error:false,data:contract,commlog:comms}
}else{
data = {error:true,message:"No Contract"}
}
res.json(data);
});
});
Where it shows var comms = contract.getCommLog(); It is never returning anything because the getCommLog() is not executing async...
I think its my misunderstanding of mongoose querying, so if you understand what I am trying to accomplish, please let me know what I am doing wrong. I have tried without async which would always return false.
The find call can return all matching results with one query, so I don't think you need async here. The reason it is not populating correctly when you call res.json(data) is because you are not waiting for the method call to finish before you fire off your server response. You would be better off nesting an additional CommLogs.find call within the Contract.find call, and only sending your response once that finishes.
//pseudo code:
Contract.find({}, function(err, contract) {
if(err || !contract) {
//return error response
}
else {
CommLogs.find({contract: contract._id}, function(err, commlogs) {
if(err || !commlogs) {
//return error response 2
}
else {
res.json({errors: false, contract: contract, commlogs: commlogs});
}
});
}
}

rendering results of multiple DB/mongoose queries to a view in express.js

given the async nature of mongoose (or sequelize, or redis) queries, what do you do when you have multiple queries you need to make before rendering the view?
For instance, you have a user_id in a session, and want to retrieve some info about that particular user via findOne. But you also want to display a list of recently logged in users.
exports.index = function (req, res) {
var current_user = null
Player.find({last_logged_in : today()}).exec(function(err, players) {
if (err) return res.render('500');
if (req.session.user_id) {
Player.findOne({_id : req.session.user_id}).exec(function(err, player) {
if (err) return;
if (player) {
current_user = player
}
})
}
// here, current_user isn't populated until the callback fires
res.render('game/index', { title: 'Battle!',
players: players,
game_is_full: (players.length >= 6),
current_user: current_user
});
});
};
So res.render is in the first query callback, fine. But what about waiting on the response from findOne to see if we know this user? It is only called conditionally, so I can't put render inside the inner callback, unless I duplicate it for either condition. Not pretty.
I can think of some workarounds -
make it really async and use AJAX on the client side to get the current user's profile. But this seems like more work than it's worth.
use Q and promises to wait on the resolution of the findOne query before rendering. But in a way, this would be like forcing blocking to make the response wait on my operation. Doesn't seem right.
use a middleware function to get the current user info. This seems cleaner, makes the query reusable. However I'm not sure how to go about it or if it would still manifest the same problem.
Of course, in a more extreme case, if you have a dozen queries to make, things might get ugly. So, what is the usual pattern given this type of requirement?
Yep, this is a particularly annoying case in async code. What you can do is to put the code you'd have to duplicate into a local function to keep it DRY:
exports.index = function (req, res) {
var current_user = null
Player.find({last_logged_in : today()}).exec(function(err, players) {
if (err) return res.render('500');
function render() {
res.render('game/index', { title: 'Battle!',
players: players,
game_is_full: (players.length >= 6),
current_user: current_user
});
}
if (req.session.user_id) {
Player.findOne({_id : req.session.user_id}).exec(function(err, player) {
if (err) return;
if (player) {
current_user = player
}
render();
})
} else {
render();
}
});
};
However, looking at what you're doing here, you'll probably need to look up the current player information in multiple request handlers, so in that case you're better off using middleware.
Something like:
exports.loadUser = function (req, res, next) {
if (req.session.user_id) {
Player.findOne({_id : req.session.user_id}).exec(function(err, player) {
if (err) return;
if (player) {
req.player = player
}
next();
})
} else {
next();
}
}
Then you'd configure your routes to call loadUser wherever you need req.player populated and the route handler can just pull the player details right from there.
router.get("/",function(req,res){
var locals = {};
var userId = req.params.userId;
async.parallel([
//Load user Data
function(callback) {
mongoOp.User.find({},function(err,user){
if (err) return callback(err);
locals.user = user;
callback();
});
},
//Load posts Data
function(callback) {
mongoOp.Post.find({},function(err,posts){
if (err) return callback(err);
locals.posts = posts;
callback();
});
}
], function(err) { //This function gets called after the two tasks have called their "task callbacks"
if (err) return next(err); //If an error occurred, we let express handle it by calling the `next` function
//Here `locals` will be an object with `user` and `posts` keys
//Example: `locals = {user: ..., posts: [...]}`
res.render('index.ejs', {userdata: locals.user,postdata: locals.posts})
});
Nowadays you can use app.param in ExpressJS to easily establish middleware that loads needed data based on the name of parameters in the request URL.
http://expressjs.com/4x/api.html#app.param

Resources