Express-Mongoose request-response cycle/error handling with exp - node.js

I was under the impression that res.send() ends the request-response cycle, but without return in the else block, next(ex) is called which passes the response object to error handling middleware, resulting in Error: Can't set headers after they are sent.
Where is my understanding off? I am using express-async-errors to catch errors, if that's important.
router.get('/', async (req, res, next) => {
// get all staff sorted by name
const allStaff = await Staff.find().sort('name');
if (!allStaff) {
res.status(404).send('No staff');
} else {
return res.status(200).send(allStaff);
}
next(ex);
});

In your question, you mention it yourselves that the next() function passes the response object to the error handling middleware, so the the next middleware will execute even if you dont want it to i.e allstaff would be sent succesfully but then the next() function would be invoked.
What you are doing (without return in the else block):
Sending the allstaff object and hence trying to end the request response cycle , but then calling next() hence calling the next middleware which tries to mess up with what would otherwise have been a successful request response cycle.
router.get('/', async (req, res, next) => {
// get all staff sorted by name
const allStaff = await Staff.find().sort('name');
if (!allStaff) {
res.status(404).send('No staff');
} else {
res.status(200).send(allStaff); //send the response..expect the req-res cycle to end
}
next(ex); //then call next which might try to hamper the outgoing response
});
What you should do instead:
If you send the response then in no way should it encounter other statement which tries to send the response again, I prefer the folloing code personally:
router.get('/', (req, res, next) => {
// get all staff sorted by name
Staff.find().sort('name').exec(function (err, docs) { //this callback function will handle results
if (err) {
next(); //if there is an error..then the next middleware will end the req response cycle
} else {
if (docs.length == 0) {
res.status(404).send('No staff'); //send response,the req- res cycle ends,never disturbed again...story finished
} else {
res.status(200).send(docs); //send response,the req- res cycle ends,never disturbed again.....story finished
}
}
});
});

Related

Return out of express route from inside mongoose callback when error

I am trying various ways of handling errors. If I have a setup to add a new document and I want to check that the name is unique, I do something like
router.route('/addSomething').post(async (req,res,next) => {
await Document.findOne(
{name: req.body.name}, // search parameter
(error,doc) = { // callback
if(error) return next(error) // pass system generated error to next() and exit route
else if (doc) return next(new Error("This name already exists")) // pass my own error to next() and exit route
})
// everything from now on shouldn't happen
await Document.save() etc...
The problem I'm having is that the route function continues even though an error has returned (I understand this is because the return statement only returns from the callback function).
I'm looking for an elegant way of handling mongoose/mongodb errors and exiting the function at point of error, without putting try/catch blocks everywhere.
When you return next(...) inside your callback function you're not actually returning from the rest of your code, only inside that function. To fix this issue I have added a continue() function inside of your code to continue the incoming HTTP request.
router.route('/addSomething').post(async (req, res, next) => {
await Document.findOne(
{ name: req.body.name }, // search parameter
(error, doc) => { // callback
if (error) return next(error) // pass system generated error to next() and exit route
else if (doc) return next(new Error("This name already exists")) // pass my own error to next() and exit route
continue(); // Call the continue function if function isn't already returned
}
);
function continue() { // Add a continue function
// Continue your code
await Document.save();
// ...
}
});
Consider using util.promisify to get rid of the callback:
router.route('/addSomething').post(async (req, res, next) => {
try {
var doc = await util.promisify(Document.findOne)(
{ name: req.body.name } // search parameter
);
} catch(error) {
return next(error); // pass system generated error to next() and exit route
}
if (doc) return next(new Error("This name already exists")); // pass my own error to next() and exit route
// everything from now on shouldn't happen
await Document.save() etc...
});

Express middleware to configure response

I want to create a middleware that automatically formats my output and returns it in a format that looks like
{
"successful": "true",
"message": "Successfully created",
"data": {
"name": "Joe",
"year": 1
}
}
Currently I'm just returning a json of the data itself (name, year, etc.)
I want to add the "successful", "message", etc.
Here's some snippets of my code below:
routes/student.js
var student_controller = require('../controllers/studentController');
router.get('/list', student_controller.student_list);
controllers/student.js
var Student = require('../models/student');
exports.student_list = function(req, res, next) {
Student.find()
.exec(function(err, list_students) {
if (err) {return next(err);}
res.json(list_students);
});
};
app.js
var studentRouter = require('./routes/student');
app.use('/student', studentRouter);
How do I make this middleware, and in which file(s) should I be calling it?
The response can be intercepted by overriding response.json function. By doing so, and adding our custom function, every time, response.json() is called, our intercepting function is triggered.
middleware/response.filter.js:
// Response Interceptor Middleware
export default (request, response, next) => {
try {
const oldJSON = response.json;
response.json = (data) => {
// For Async call, handle the promise and then set the data to `oldJson`
if (data && data.then != undefined) {
// Resetting json to original to avoid cyclic call.
return data.then((responseData) => {
// Custom logic/code. -----> Write your logic to add success wrapper around the response
response.json = oldJSON;
return oldJSON.call(response, responseData);
}).catch((error) => {
next(error);
});
} else {
// For non-async interceptor functions
// Resetting json to original to avoid cyclic call.
// Custom logic/code.
response.json = oldJSON;
return oldJSON.call(response, finalResponse);
}
}
} catch (error) {
next(error);
}
}
In the Server.js file, register the middleware:
// Server.js file
import externalResponseFilter from "./middleware/response.filter.js:";
// Create Express server
const app = express();
// Response interceptor - Initialization.
app.use(externalResponseFilter);
And in the controller where you are returning the response, return with response.json() function instead of response.send().
Let me know if any additional explanation is required.
I think there is no need to middleware in your case unless you need an auth check for this routing. The Middleware has the functionality to handle something before getting your response.
your code shall be:
router.get('/list',(req,res,next)=>{
Student.find().then(data=>res.json(data)).catch(err=>conole.log(err)) });

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

How to resolve next is not a function error message?

I am trying to create a MEAN stack app, I'm currently working on the UPDATE functionality.
My code is currently failing when it runs into this method:
businessRoutes.route('/update/:id').post(function (req, res) {
Business.findById(req.params.id, function (err, business) {
if (!business)
return next(new Error('Could not load Document'));
else {
business.person_name = req.body.person_name;
business.business_name = req.body.business_name;
business.business_gst_number = req.body.business_gst_number;
business.save().then(business => {
res.json('Update complete');
console.log('Update Complete');
})
.catch(err => {
res.status(400).send("unable to update the database");
});
}
});
});
The error message being displayed in the console is:
TypeError: next is not a function
It's failing on this line of code:
return next(new Error('Could not load Document'));
Can someone please tell me why this is occurring & how I can resolve it?
The second parameter to findById expects a callback that has two arguments, err and <entity>. There's no middleware or something else in place, what you call next(...) tries to call your found entity.
From the docs
Adventure.findById(id, function (err, adventure) {});
You see, in your case, business is always undefined, and adventure or next is never a function.
Here is what you probably meant:
The problem is that the param that you mean to be "next" actually comes from express.js (which means it comes in on the first function call back that you ran (after .post(---this function--- has 3 params that you may chose to use:
req the request that the user made to your server
res the response that you are getting ready to send
next the 3rd param is optional and allows you to send to the next middleware or if you send it a parameter it will send it to the error handler which will send your error as a response.
On the other hand:
you placed the next param randomly in the middle of the mongoose function that you attempted to call: the mongoose function actually only takes (err, item)...
businessRoutes.route('/update/:id').post(function (req, res, next) {
// note that express exposes the "next" param
// this is a callback indicating to run the `next` middleware
Business.findById(req.params.id, function (err, business, whoKnows) {
// note this is from mongoose.js: the callback function actually only has 2 parameters (err, and foundObject)
if (!business)
return next(new Error('Could not load Document'));
else {
business.person_name = req.body.person_name;
business.business_name = req.body.business_name;
business.business_gst_number = req.body.business_gst_number;
business.save().then(business => {
res.json('Update complete');
})
.catch(err => {
res.status(400).send("unable to update the database");
});
}
});
});
Be advised, that I'd recommend using async await and that would make the whole thing much easier to understand:
businessRoutes.route('/update/:id').post(async (req, res, next) => {
try {
const foundBusiness = await Business.findById(req.params.id)
//... do stuff
catch (e) {
next(throw new Error(e))
// .. other stuff
}
})

next() promise error handling

What would be the optimal way for error handling?
I need custom json error messages. It's an API.
exports.putCurso = function (req, res, next) {
util.updateDocument(req.curso, Curso, req.body);
req.curso.saveAsync()
.then(function (data) {
return res.status(201).json({message: 'Curso atualizado.', data: data});
})
.catch(function(error) {
return res.status(500).json({message: 'ERROR!'});
//OR return next(error); but I need custom json error messages so it doesn't make sense
})
.finally(next); //OR return next(error)? redundant?
};
I am no mongoose guy but I know one or two things about express and promise
exports.putCurso = function (req, res, next) {
util.updateDocument(req.curso, Curso, req.body);
req.curso.saveAsync()
.then(function (data) {
res.status(201).json({message: 'Curso atualizado.', data: data});
}, function(error){
res.status(500).json({message: 'ERROR!'});
})
};
And this is basically all that you need. Based on the implementation, this is probably a normal route because it always returns something (res.json) to the client. Therefore, you don't have to call next because it is meant for middlewares to call
Also you don't have to return anything because when you call res.json, it basically says that this request ends here, nothing else.
Last but not least, by specification, promise then supports 2 functions, the first one is for handing successful case, the 2nd one is for exceptions. So, you don't have to call catch
Considering Curso a mongoose document
You can do it like this
req.curso.save(function(err,data){
if(err) res.status(500).json({message: 'ERROR!'});
else res.status(201).json({message: 'Curso atualizado.', data: data})
});
EDIT : if you have so many similar issues through out your little huge node application, its worth looking at rb, then you can do it like
var RB = require('rb');
exports.putCurso = function (req, res, next) {
util.updateDocument(req.curso, Curso, req.body);
// the below line could have been written in some middleware (eg middleware provided by express.io), so we do get clear code in controller part.
res.RB = RB.build(res, { // you may customize your builder yours way, after looking into `rb` docs
errorStatus : 500, successStatus : 201,
errorKey : false, successKey : 'data',
preProcessError : function(){ return { message : 'ERROR!' } },
addToSuccess : { message : 'Curso atualizado.' }
});
//Now only one line in controller
req.curso.save(res.RB.all);
};
Disclosure : i am author of rb.
asCallback takes a callback which it calls with the promise outcome mapped to the callback convention:
If the promise is rejected, it calls the callback with the error as first argument: cb(error)
If the promise is fulfilled, it calls the callback with the value as the second argument: cb(null, value).
exports.putCurso = function (req, res, next) {
util.updateDocument(req.curso, Curso, req.body);
req.curso.saveAsync()
.then(function (data) {
return res.status(201).json({message: 'Curso atualizado.', data: data});
})
.catch(function(error) {
return res.status(500).json({message: 'ERROR!'});
//OR return next(error); but I need custom json error messages so it doesn't make sense
})
.asCallback(next);
};

Resources