How to catch 4xx and 5xx errors in express? - javascript

Lets say that a user is creating an account, but the inputs aren't valid, so I send the errors to the frontend and return a 409 status code. But when the error is in the server side, I don't want to return a 409 status code, but a 5xx status code. How can I achieve this?
exports.signup = (req, res) => {
try {
//pseudocode
db.createUser(req.user);
res.status(201).send("user created!");
} catch (err) {
const errors = { username: "", password: "" };
//pseudocode
forEach((error) => {
if(validation.error.username) {
errors.username = validation.error.username
} else {
errors.password = validation.error.password
})
res.status(409).json(errors);
}
};

Instead of handling errors inside each route, consider using set of error handling middleware, like described in this article. For example:
app.use(function handleNotFoundError(error, req, res, next) {
if (error instanceof NotFoundError) {
return res.status(HttpStatus.NOT_FOUND).send({
httpStatus: HttpStatus.NOT_FOUND,
message: error.message,
error: {}
});
}
next(error);
});
app.use(function handleValidationError(error, req, res, next) {
if (error instanceof ValidationError) {
return res
.status(HttpStatus.BAD_REQUEST)
.json({
httpStatus: HttpStatus.BAD_REQUEST,
message: error.message,
validationErrors: error.validationErrors
});
}
next(error);
});
// ... etc.
The benefit of this approach is creating an error-handling layer, essentially wrapping your routing code and the corresponding services. The latter then should only worry about throwing correct errors (both by types and related data).
You can take this approach even further by creating so-called Error Router: map of the functions, using NotFoundError, ValidationError etc. as keys and corresponding handlers as values. Once again, the benefit is that you won't have to worry about sending 'proper' codes and data inside your controllers and/or services; its responsibility ends up on throwing correct errors.

You could validate the inputs on a first step and return a 4xx in case they are invalid. If they are valid, in a second step, you could store the data and return a 5xx status code if an error happens server-side.

Related

How to add an error handler in NodeJS after all routes defined by user?

I am writing a library that requires the user to handle errors generated by my library. The way I do it now is that I tell users to add an error handler after all their routes like so:
const app = express();
app.use(MyLibrary.init())
// <user's routes here>
app.use(MyLibrary.errorHandler())
app.use((err, req, res, next) => {
// user's generic error handler
})
The init function in my lib is as follows:
function init() {
return async (request, response, next) => {
try {
// some logic here that modifies the request object and can throw an error
return next(); // this can also throw an error, or generate an unhandled rejection / error
} catch (err) {
next(err);
}
};
}
The errorHandler function is as follows:
function errorHandler() {
return async (err, request, response, next) => {
try {
if (/*err from MyLibrary*/) {
// do something and send a response
return;
}
next(err);
} catch (err) {
next(err);
}
};
}
My aim is to make it so that the user doesn't need to add app.use(MyLibrary.errorHandler()).
One solution that comes to mind is that in the init function, instead of calling next(err), I can handle my library's error directly. However, errors from MyLibrary can also be generated in any of the API handlers that the user writes (since they can interact with my library via the modified request object). These API handlers may be async.
Is there any solution to this issue?
Thank you

Node.JS returns a 404 on interrogation of an endpoint that actually exists

I have a web application, started by a previous company, written in Angular.JS. The application exposes a request towards the back-end (written in Node.JS+Express) to gather some data required to fill a table. Specifically, this is the request that the application sends everytime the user enters in the page that holds the table (The config variable holds the access token).
return $http.get(API + '/api/myPath/for/Having/Data', config).then(handleSuccess, handleError);
handleSuccess and handleError are so defined
handleSuccess: function (res) {
debugger;
var deferred = $q.defer();
res.data.success ? deferred.resolve(res.data) : deferred.reject(res.data.message);
return deferred.promise;
},
handleError: function (error) {
return {
success: false,
message: error
};
}
In my back-end I've put an a listener to whatever gets called with the "/api" prefix, like this
app.use('/api', authorization.validateToken);
And another listener, that should work only if there is no match (written at the very end of the file that handles all the general inquiries of the app)
app.all('*', (req, res) => {
console.log('Hi, Stack Overflow!');
res.send({
success: false,
status: 404,
message: 'Invalid Uri Resource'
});
});
And, lastly, this is the endpoint that should get called in the back-end from Angular.js
app.get('/api/myPath/for/Having/Data', something.somethingToCall);
Here's the funny part: for a reason that I still have to understand, Angular.JS calls that endpoint twice, resulting in one failing procedure (404) and another one that goes smoothly (200).
The operation flow should be like this: Angular calls the back-end --> Node checks the validity of the token --> executes operation if everything goes okay.
The operation is called twice (seen thanks to the Visual Studio Code debugger and Chrome's Network Monitor) and, even though the token's validation process is correctly executed everytime, the first time the next() function will hold the app.all() listener.
Also,even before I start debugging the first request that is sent out, the JavaScript console on Google Chrome warns me that there has been an error such as like "Cannot read property 'data' of undefined", meaning that the request gets executed twice with the first time returning a 404.
exports.validateToken = (req, res, next) => {
console.log(`check user here`);
// next();
var token = //I take the token
console.log(token);
if (token) {
jwt.verify(token, require('../../secret'), (err, decoded) => {
if (err) {
res.send({
success: false,
status: 500,
tokenExpired: true,
message: "Effettua nuovamente l'accesso"
});
} else {
req.decoded = decoded;
next();
}
});
} else {
res.send({
success: false,
status: 406, // Fprbidden
message: 'User not Authenticated'
});
}
};
Does anybody know how to help me somehow?
EDIT: this is an example of how Chrome's sees both requests. The screenshot, in particular, refers to the first one that gets called and produces the 404
The CORS is handled in the back-end like this
app.use(function (req, res, next) {
if (req.headers.origin && (req.headers.origin.match("http:\/\/somewebsite.com.*") || req.headers.origin.match("http:\/\/localhost:8010") )) {
res.header("Access-Control-Allow-Origin", req.headers.origin);
}
next();
});
Also, I'm adding the endpoint that needs to be called. This also exploits MongoDB + Mongoose for querying the DataBase and return stuff to the front-end. The parameters that I'm passing are pageSize (how many elements per page) and the current page number
exports.getAds = (req, res) => {
var criteria = req.body || {};
var pageSize = criteria['pageSize'] ? Number(criteria['pageSize']) : undefined;
var pageNumber = criteria['pageNumber'] ? Number(criteria['pageNumber']) : undefined;
var sort = criteria.sort || { createdAt: 'desc' };
if (criteria.customerName) criteria.customerName = { $regex: `.*${criteria.customerName}.*`, $options: 'i' };
if (criteria.spentEuros) criteria.spentEuros.$gte = criteria.spentEuros;
if (criteria.referralMail) criteria.referralMail = { $regex: `.*${criteria.referralMail}.*`, $options: 'i' };
console.log(criteria);
var columns = "customerName duration spentEuros";
if (pageSize && pageNumber) {
Adv.paginate(criteria, {
page: pageNumber,
limit: pageSize,
select: columns,
sort: sort
}, function (err, result) {
if (!err) res.status(200).send({ success: true, data: result });
else res.status(500).send({ success: false, message: err });
});
} else {
Adv.find(criteria)
.select(columns)
.sort(sort)
.exec(function (err, result) {
if (!err) res.status(200).send({ success: true, data: result });
else res.status(500).send({ success: false, message: err });
});
}
};
EDIT2: Solution to the question: adding an app.options listener in the back-end (as pointed out by #slebetman), alongside with the already existing app.get one, solved the issue
Here's the funny part: for a reason that I still have to understand, Angular.JS calls that endpoint twice...
That sounds a lot like the browser sending a CORS preflight OPTIONS request, followed by a GET. Check the HTTP verb being used, and be sure you're handling OPTIONS (not just GET) if you need to support CORS on your endpoint. (If you're not expecting this to be a cross-origin request, check the origin of the page relative to the origin of the API call, something [protocol, port, domain] seems to be different — if it's an OPTIONS call.)

How to throw a 404 error in express.js?

In app.js, I have
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
so if I request some not exist url like http://localhost/notfound, above code will execute.
In exist url like http://localhost/posts/:postId, I would like to throw 404 error when access some not exist postId or deleted postId.
Posts.findOne({_id: req.params.id, deleted: false}).exec()
.then(function(post) {
if(!post) {
// How to throw a 404 error, so code can jump to above 404 catch?
}
In Express, a 404 isn't classed as an 'error', so to speak - the reasoning behind this is that a 404 isn't usually a sign that's something's gone wrong, it's just that the server couldn't find anything. Your best bet is to explicitly send a 404 in your route handler:
Posts.findOne({_id: req.params.id, deleted: false}).exec()
.then(function(post) {
if(!post) {
res.status(404).send("Not found.");
}
Or alternatively, if this feels like too much repeated code, you could always pull that code out into a function:
function notFound(res) {
res.status(404).send("Not found.");
}
Posts.findOne({_id: req.params.id, deleted: false}).exec()
.then(function(post) {
if(!post) {
notFound(res);
}
I wouldn't recommend using a middleware in this situation solely because I feel like it makes the code less clear - the 404 is the direct result of the database code not finding anything, so it makes sense to have the response in the route handler.
I have the same app.js structure, and I solved this problem in this way in the route handler:
router.get('/something/:postId', function(req, res, next){
// ...
if (!post){
next();
return;
}
res.send('Post exists!'); // display post somehow
});
The next() function will call the next middleware which is the error404 handler if it is right after your routes in the app.js.
You can use this and the end of your routers.
app.use('/', my_router);
....
app.use('/', my_router);
app.use(function(req, res, next) {
res.status(404).render('error/404.html');
});
you're probably looking for something like https://github.com/expressjs/api-error-handler
or just https://github.com/jshttp/http-errors
Even though 404 pages are not considered an error in Express as written here, its really damn handy if you DO handle them like so. For instance when you are developing an API that wants consistent JSON output. The following code should help you with that:
Define a helper function abort to create status errors that can be easily used in your code to pass to the next function:
// Use the `statuses` package which is also a dependency of Express.
const status = require('statuses');
const abort = (code) => {
const err = new Error(status[code]);
const err.status = code;
return err;
};
Define the catch-all middleware for 404 pages which should be defined at the bottom of your stack (after all routes have been added). This forwards the 404 as an error:
app.use((req, res, next) => {
next(abort(404));
});
Lastly, the final error handler will now consistently send all errors in JSON format:
app.use((err, req, res, next) => {
if(!res.headersSent) {
// You can define production mode here so that the stack trace will not be sent.
const isProd = false;
res.status(err.status || 500).json({
error: err.toString(),
...(!isProd && {stack: err.stack.split('\n').map(i => i.trim())}),
});
}
next(err);
});

Mocha test cases throwing Error: expected 200 "OK", got 403 "Forbidden"

I am using mochajs for my mean applications server side test cases.The test cases are showing an error, Error: expected 200 "OK", got 403 "Forbidden".
I tried using console logs inside the functions that I am trying to test, but they never get executed. I am unable to find out the issue. Can someone help me with this.
My controller
exports.signin = function(req, res) {
req.body.username = new Buffer(req.body.username, base64').toString('ascii');
User.findOne({
username: req.body.username.toLowerCase()
}, function(err, user) {
if (err) {
//do something
} else if (!user) {
//do something
} else {
//do something
}
});
};
My test case
it('should be able to signin if the credentials are valid', function(done) {
var obj = {
username: new Buffer('demo').toString('base64'),
password: new Buffer('demouser').toString('base64')
};
agent.post('/auth/signin')
.send(obj)
.expect(200)
.end(function(signinErr, res) {
if (signinErr){
console.log(signinErr);
done(signinErr);
}
else{
var temp = res.body;
console.log(temp);
done();
}
});
});
And my route
app.route('/auth/signin').post(users.signin);
I was facing the same issue. I found out that we were forcing SSL in our application using the middleware express-force-ssl by app.use(force-ssl). We used a flag to control this behavior. When I made that false, the test cases ran fine.
So if you are not controlling it with the flag. Try removing that module and running your test cases.

How to properly handle errors in Express?

I am beginning to work with Express JS and have run into an issue. I can't seem to figure out the proper way to handle errors.
For example, I have a web services API that serves an object called "event". I'd like to return a simple string of "cannot find event" when a user submits an event id that isn't found. Here is how I'm currently structuring my code:
app.get('/event/:id', function(req, res, next) {
if (req.params.id != 1) {
next(new Error('cannot find event ' + req.params.id));
}
req.send('event found!');
});
When I submit an id other than 1, Node crashes with the following output:
http.js:527
throw new Error("Can't set headers after they are sent.");
^
Error: Can't set headers after they are sent.
at ServerResponse.<anonymous> (http.js:527:11)
at ServerResponse.setHeader (/usr/local/kayak/node_modules/express/node_modules/connect/lib/patch.js:62:20)
at /usr/local/kayak/node_modules/express/node_modules/connect/lib/middleware/errorHandler.js:72:19
at [object Object].<anonymous> (fs.js:107:5)
at [object Object].emit (events.js:61:17)
at afterRead (fs.js:878:12)
at wrapper (fs.js:245:17)
From what I can tell by using the node.js debugger, execution of the block of code continues after next() is called, meaning that req.send('event found!') tries to run. I don't want this to happen.
The only workaround that I've found is to simply throw a new Error() instead of "next-ing" it, but this results in a default Express HTML error page being generated. I'd like a little more control than that.
I have taken the time to read over the error handling section of the Express documentation, but I couldn't make sense of it.
You'll want to check out Express Error Handling. From there:
app.param('userId', function(req, res, next, id) {
User.get(id, function(err, user) {
if (err) return next(err);
if (!user) return next(new Error('failed to find user'));
req.user = user;
next();
});
});
The sweetspot that you are missing is the return next(...)
That's because you're doing it wrong: you already threw an Error (which will be processed by Express and return a 500 - Error page for the user or something like that) but you are also trying to send your own response to the client: res.send('event found!');
You should really check out the Express guide about Error Handling here: http://expressjs.com/guide/error-handling.html
What I would do in your example is:
function NotFound(msg){
this.name = 'NotFound';
Error.call(this, msg);
Error.captureStackTrace(this, arguments.callee);
}
app.get('/event/:id', function(req, res, next){
if (req.params.id != 1) {
throw new NotFound('Cannot find event ' + req.params.id);
} else {
res.send('event found!');
}
});
app.error(function(err, req, res, next){
if (err instanceof NotFound) {
res.render('404.ejs');
} else {
next(err);
}
});
You have a couple of problems in your code:
When responding to the client, you need to use the response object (res rather than req).
When sending an error to next, you should return, so the rest of the function doesn't run.
Here's your code after fixing those errors:
app.get('/event/:id', function(req, res, next) {
if (req.params.id != 1) {
return next(new Error('cannot find event ' + req.params.id));
}
res.send('event found!'); // use res.send (NOT req.send)
});

Categories