Node: simulating network failure - res.status(404) not working - node.js

class gameInfo {
static async gameeee(req, res) {
try {
console.log(req.body);
await db.adb
.collection("game")
.findOne({ req.body.gameID}, async (err, result) => {
console.log("a");
if (err) {
console.log("b");
res.status(400);
} else if (result === null) {
console.log("c"); <------- this is called
res.status(404); <------ not happening
} else if (result !== null) {
res.json({ result });
}
});
} catch (err) {
console.log(err);
res.status(400);
}
}
}
console result is
a
c
I am trying to simulate the response failure due to no data. However, res.status(404) is not working. How can I send the error?
Also, I am super confused with among res.send, res.status and res.sendStatus. What are the differences using these three?

You still need to send or end the response, see https://expressjs.com/en/api.html#res.status:
res.status(404).end();
And yes, as the documentation says, you could just use sendStatus instead.
res.sendStatus(404) // equivalent to res.status(404).send('Not Found')

Related

Conditionally send responses in an Express app

I'm curious whether you can write if statements in an Express app to conditionally execute your code without providing else statements.
if(pred) {
doSomething()
}
return foo;
calcBar(); // doesn't run.
Above is the synchronous code that stops execution after the return statement.
My Express function looks like this:
app.get('/matches', async function(req, res) {
try {
const data = await someGraphQLCall();
if(data.length === 0) {
res.json({ message: "No data." });
}
const someOtherData = await someOtherGraphQLCall(data.foo);
res.json({ someOtherData });
} catch (err) {
res.json({err})
}
}
I know because of this question that code after the first res.json might still be executed. Is there a way to stop that? I don't want the second GraphQL call to execute if the first if condition is met. Is that possible without using else ?
Edit:
As the question I linked above mentioned, using a return statement is a bad option because:
it also makes it less meaningful and vague, cause it uses incorrect semantics. If you are not using the value from the function, then you shouldn't return one.
You can use return keyword on the first response to immediately return from the function.
app.get('/matches', async function(req, res) {
try {
const data = await someGraphQLCall();
if(data.length === 0) {
return res.json({ message: "No data." });
}
const someOtherData = await someOtherGraphQLCall(data.foo);
res.json({ someOtherData });
} catch (err) {
res.json({err})
}
}
Edit:
As an alternative, you can split the logic of the data and building up response. This way you can use return and it's easier to read:
app.get('/matches', async function (req, res) {
try {
const data = await getDataFromGraphQLCall();
res.json(data);
} catch (err) {
res.json({ err })
}
});
async function getDataFromGraphQLCall() {
const data = await someGraphQLCall();
if (data.length === 0) {
return { message: "No data." };
}
const someOtherData = await someOtherGraphQLCall(data.foo);
return { someOtherData };
}
If you are wondering if there is a way to achieve that without the else, yes it is.
But, It might not be THE cleanest way. IMO, using return is the best way to stop the execution of the controller.
Anyways, You can split the chunk of code into middlewares and use ternary operator to conditionally send responses.
In your example, separate out data = await someGraphQLCall(); as follows:
const middlewareOne = async function(req, res, next) {
let data = [];
let response = { message: "No data." };
try {
data = await someGraphQLCall();
req.locals.data = data; // <- attach the data to req.locals
} catch (err) {
response = { err };
}
data.length === 0 ? res.json(response) : next();
};
And then, mount the middlewareOne BEFORE your controller:
app.get("/matches", middlewareOne, async function controller(req, res) {
try {
const someOtherData = await someOtherGraphQLCall(req.locals.data.foo);
res.json({ someOtherData });
} catch (err) {
res.json({ err });
}
});
How this works is, the controller function would only be executed by express if the next() is called from the previous middleware -- middlewareOne in the example.
And as middlewareOne only calls next() if the data.length is not 0, it would work as you expected.
For more information on passing data from one middleware to other, read this
The return statement terminates the function execution in this context. In my opinion, you should handle the success case then the error case since the code will be read top to bottom.
In if statement, data could be undefined or null.
You can read more here: MDN - return
app.get('/matches', async function(req, res) {
try {
const data = await someGraphQLCall();
// alternative, if (data && data[0]) {
if (data && data.length) {
const someOtherData = await someOtherGraphQLCall(data.foo);
return res.json({ someOtherData });
}
return res.json({ message: "No data." });
} catch (err) {
console.log(err); // log error with logger and drain to loggly.
res.json({ err })
}
}
With Void operator:
Void operator allows you to return undefined but evaluate the given expression.
You can read more here: MDN - Void
app.get('/matches', async function(req, res) {
try {
const data = await someGraphQLCall();
// alternative, if (data && data[0]) {
if (data && data.length) {
const someOtherData = await someOtherGraphQLCall(data.foo);
return void res.json({ someOtherData });
}
return void res.json({ message: "No data." });
} catch (err) {
console.log(err); // log error with logger and drain to loggly.
res.json({ err })
}
}

Promise-chain vs. simplicity

I have a Node 8 / Express 4 / Mongoose 4 API and would like to generalize some code so that I can reuse it for other parts.
Consider the following code that would create a new user:
function postUser(req, res, next) {
var body = req.body;
if ("data" in body) {
var user = new User(body.data);
user.save(function(err, savedUser) {
if (err) {
if (err.name === 'MongoError' && err.code === 11000) {
// user already exists
res.status(400).json({status: "fail", message: "User already exists"});
} else {
return next(err);
}
} else {
// user successfully saved
res.json({status: "success", data: savedUser});
}
});
} else {
// malformed body
res.status(400).json({status: "fail", message: "Malformed body"});
}
}
Let's assume that I have other functions that would do similar work and some of them are callback-hell. How would I best generalize the above code? I thought about using promise-chains like this:
function postUser(req, res, next) {
validateBody(req.body)
.then(createNewUser)
.then(user => sendUser(user, res))
.catch(e => handleErrors(e, res));
}
function validateBody(body) {
return new Promise(function(resolve, reject) {
if ("data" in body) {
resolve(body.data);
} else {
reject(new InvalidBodyError());
}
});
}
function createNewUser(userObj) {
return new Promise(function(resolve, reject) {
var user = new User(userObj);
user.save(function(err, savedUser) {
if (err) {
if (err.name === 'MongoError' && err.code === 11000) {
// user already exists
reject(new UserAlreadyExistsError(userObj));
} else {
// other error
reject(err);
}
} else {
// user successfully saved
resolve(savedUser);
}
});
});
}
function handleErrors(e, res) {
if (e instanceof InvalidObjectIdError) handleInvalidObjectIdError(e, res)
else if (e instanceof UserNotFoundError) handleUserNotFoundError(e, res)
else if (e instanceof InvalidBodyError) handleInvalidBodyError(e, res)
else if (e instanceof UserAlreadyExistsError) handleUserAlreadyExistsError(e, res)
// TODO: handle unknown errors
}
As you can see, it looks cleaner and more reusable. But how will it perform under load? I am especially concerned about creating multiple promises per request. Does this scale or not?
Another way of solving it would be to create a generic base class that would solve the generic stuff and then extend this class with implementation-specific methods (pseudocode):
class Action {
constructor() {}
postDoc(Base, req, res, next) {
var body = req.body;
if ("data" in body) {
var doc= new Base(body.data);
doc.save(function(err, savedDoc) {
if (err) {
if (err.name === 'MongoError' && err.code === 11000) {
// docalready exists
res.status(400).json({status: "fail", message: "Doc already exists"});
} else {
return next(err);
}
} else {
// user successfully saved
res.json({status: "success", data: savedDoc});
}
});
} else {
// malformed body
res.status(400).json({status: "fail", message: "Malformed body"});
}
}
}
class UserAction extends Action {
constructor() {
super();
}
postUser(body, req, res, next) {
this.postDoc(User, req, res, next);
}
}
class AnotherAction extends Action {
constructor() {
super();
}
postAnother(body, req, res, next) {
this.postDoc(AnotherBase, req, res, next);
}
}
And then just use UserAction or AnotherAction (User is a mongoose model in my case).
Which one do you prefer?
I thought about using promise-chains like this, which is cleaner and more reusable. But how will it perform under load?
Just fine.
I am especially concerned about creating multiple promises per request. Does this scale or not?
Yes. Promises are cheap. Look at how many other objects and callback closures you are creating per request - it scales exactly the same.
However, you can further simplify:
function validateBody(body) {
return "data" in body
? Promise.resolve(body.data)
: Promise.reject(new InvalidBodyError());
}
function createNewUser(userObj) {
return new Promise(function(resolve, reject) {
new User(userObj).save(function(err, savedUser) {
if (err) reject(err);
else resolve(savedUser);
});
}).catch((err) => {
if (err.name === 'MongoError' && err.code === 11000) {
// user already exists
throw new UserAlreadyExistsError(userObj);
} else {
// other error
throw err;
});
});
}
Another way of solving it would be to create a generic base class that would solve the generic stuff and then extend this class with implementation-specific methods
No, don't do that. Inheritance is the wrong tool here. Creating generic helper functions like postDoc, which abstracts over the type of the document to create, are a good idea, but there's no good reason to put them in classes. Combine them with promises. If the number of parameters to the helper function gets out of hand, you can use objects, and even a class, but don't use inheritance - use different instances instead. For example, the code could look like this:
const userAction = new Action(User, UserAlreadyExistsError);
const anotherAction = new Action(AnotherBase, …);
function post(action) {
return (req, res, next) => {
action.postDoc(req)
.then(doc => send(doc, res))
.catch(e => handleErrors(e, res));
};
}

Error: can't set headers after they are sent

I want to send an argument with res.redirect(). However, I'm getting an error while running it, saying that I cannot set headers after they are sent.
What does that mean, and how can I fix it?
app.post('/updateCollaborateRequest', function(req,res) {
if(req.body.accept == true) {
Team.findOne({'name': req.body.data.teamName}, function (err, team) {
if(err) {
res.redirect('/explore');
}
team.accepted = true;
team.save(function (err) {
if (err) {
alert(err);
}
Request.findOne({'emailAdmin': req.session.email}, function(err, request) {
request.seen = true;
request.save(function(err) {
if(err) {
console.log(err);
}
});
});
res.redirect("/teamprof/" + team.name);
});
});
}
Request.findOne({'emailAdmin': req.session.email}, function(err, request) {
request.seen = true;
request.save(function(err) {
if(err) {
console.log(err);
}
res.render('userprof1', {message : req.flash('done')});
});
});
});
Your code is continuing after redirecting. That is probably the problem. You should return, otherwise you are going to keep trying to write to the HTTP response.
This particular error message is caused by code paths that lead to multiple res.xxx() calls that try to send the response more than once.
You have multiple places where you are doing that. For example, you have two res.redirect() calls inside the Team.findOne() callback, but then you proceed with Request.findOne() where you have a res.render(). You HAVE to make sure that you only send the response once.
I'm not entirely sure what the desired logic is in all cases, but you can fix that error by adding an else statement before the Request.findOne() and adding a return after each res.redirect(). If this is not the exactly flow you want, then please explain more about how you want the control flow to work. Here's the code with those changes applied:
app.post('/updateCollaborateRequest', function(req,res) {
if(req.body.accept == true) {
Team.findOne({'name': req.body.data.teamName}, function (err, team) {
if(err) {
res.redirect('/explore');
return;
}
team.accepted = true;
team.save(function (err) {
if (err) {
// FIXME: need error handling here
alert(err);
}
Request.findOne({'emailAdmin': req.session.email}, function(err, request) {
request.seen = true;
request.save(function(err) {
if(err) {
// FIXME: need error handling here
console.log(err);
}
});
});
// Are you sure you want to send this response before
// you even know if the `Request.findOne()` and `request.save()`
// have been sucessful?
res.redirect("/teamprof/" + team.name);
return;
});
});
} else {
Request.findOne({'emailAdmin': req.session.email}, function(err, request) {
request.seen = true;
request.save(function(err) {
if(err) {
console.log(err);
}
res.render('userprof1', {message : req.flash('done')});
});
});
}
});
You still have several error conditions for which no response is sent which is incomplete error handling so those need to be fixed too. And, I've added some comments in the code about some other suspect things in the code.

Sending express response with data from 2 different mongoose queries

I need to query 2 different collections and send it in the express response. I have a very vague idea of what is needed to do so. I tried to contact the query documents to an empty array and send that new array as the response. But I receive an empty array as a response.
This is my route.
site.route('/campus/development')
.get(function(req, res) {
var devPosts = [];
development.find().exec(function(err, docs) {
if (err) {
console.log('Error : ' + err);
} else {
if (docs != null) {
devPosts = devPosts.concat(docs);
console.log(docs);
} else {
console.log('No posts found');
}
}
});
jobs.find().exec(function(err, jobs) {
if (err) {
console.log('Error : ' + err);
} else {
if (jobs != null) {
devPosts = devPosts.concat(jobs);
console.log(jobs);
} else {
console.log('No jobs');
}
}
});
res.send(devPosts);
});
This is due to the async operation of the requests to the database. There are a variety of solutions to this but basically distill down to two types: callbacks or promises.
A callback solution might look like:
site.route('/campus/development')
.get(function(req, res) {
development.find().exec(function(err, devDocs) {
if (err) {
console.log('Error : ' + err);
} else {
if (devDocs != null) {
console.log(devDocs);
jobs.find().exec(function(err, jobs) {
if (err) {
console.log('Error : ' + err);
} else {
if (jobs != null) {
console.log(jobs);
res.send([devDocs, jobs]);
} else {
console.log('No jobs');
}
}
});
} else {
console.log('No posts found');
}
}
});
});
But this introduces a couple of interesting issues: one is the phenomenon known as callback hell and the other is that you should be responding with the errors which means you would need to have a response call for each error (albeit this is a very simplistic approach to it).
As mentioned earlier there is another type of solution which involves using promises. There are a bunch of libraries that you can use and actually Mongoose returns a promise from the exec method. However if you are on Node 0.12.x you can also use the native Promise (it was introduced in 0.11 but you should be using 0.12.x over 0.11.x). A benefit to using the native promise over the one returned from Mongoose is that you can execute these requests in parallel since they don't depend on each other.
site.route('/campus/development')
.get(function(req, res) {
Promise.all([
development.find().exec(), // returns a promise
jobs.find().exec() // returns a promise
]).then(function(results) {
// results is [devDocs, jobs]
console.log(results);
res.send(results);
}).catch(function(err) {
res.send(err);
});
});

Mongoose .findOne not working as an internal function call

With this as a URL:
'api/support-tag/name/myTagName'
This function works properly:
getByName: function (req, res) {
model.Shared_SupportTag.findOne({name: req.params.name}).exec(function (err, results) {
if (err) {
return res.status(400).send({
message: errMsg.Util_ErrorMsg.getErrorMessage(err)
});
}
res.send(results);
})
}
But when I try to call a similar function from within the node server:
supportDoc.category = GetById(item.category);
function GetById(name){
model.Shared_SupportTag.findOne({name: name}).exec(function(err, result){
if(err){
console.log(err)
}else{
console.log(result);
}
})
}
The function does not execute, nor does the error catch, intellisense shows:
err= Reference error; err is not defined
result = Reference error; result is not defined
All I am trying to accomplish is a function call from within the server and not via a URL.
Any solution here? Thanks in advance
In the case of the findOne() method, the positive response (sans error) will either hold a mongoose object or null.
If the same query had been sent using just find(), the result would have been an empty array.
function GetById(name){
model.Shared_SupportTag.findOne({name: name}).exec(function(err, result){
if(err){
console.log(err)
}else{
if (result) console.log(result); //Check whether object exists.
else console.log('Not found!');
}
})
}
Solved:
model.Shared_SupportDoc.find({}).exec(function (err, collection) {
var supportDocs = require('../../data/_seed/support/supportDocs.json');
if (collection.length === 0) {
supportDocs.forEach(function (item) {
var supportDoc = new model.Shared_SupportDoc;
supportDoc.title = item.title;
supportDoc.created = item.date;
supportDoc.icon = item.icon;
supportDoc.likeCount = item.likeCount || 7;
-----> // requires callback - ie asynchronous
GetByName(item.category, function(tagId) {
supportDoc.categoryId = tagId;
-----> // must put save in the callback
supportDoc.save(function (err) {
if (err) {
console.log(supportDoc.categoryId)
console.log('Error: ' + err);
} else {
console.log('Support Doc Seed Complete');
}
});
});
})
}
});}
function GetByName(name, next) {
model.Shared_SupportTag.findOne({name : name}).exec(function (err, result) {
if (!result) {
console.log('Not Found');
next();
} else {
console.log(result._id);
next(result._id);
}
});}

Resources