Environment: node.js, Express, express-session package.
Background: I was testing an Express error handler and I got an error that I wasn't expecting.
The following simplified route throws an error.
TypeError: Cannot read property 'userValues' of undefined
exports.errorHandler = wrapAsync(async function(error, req, res, next) {
let loggedIn = req.session.userValues ? true : false;
res.render('error', { loggedIn });
});
However when I remove the error parameter it works without an error as I had anticipated.
exports.errorHandler = wrapAsync(async function(req, res, next) {
let loggedIn = req.session.userValues ? true : false;
res.render('error', { loggedIn });
});
Why might this be?
The basic pattern in the second example works in several dozen routes that don't include the error parameter.
You could use something like this. And it will only get executed whenever there is an ERROR 500 unless you passed the ERROR 404 to this one using next() function, if you handled all the errors correctly you should be able to make an ERROR 500 and this should be able to catch that ERROR.
const errorHandler = require("./your-file");
...
... every other route even error 404 handler
...
app.use(errorHandler);
What do I mean by using next() for ERROR 404
If you have used express-generator then you should already have this piece of code:
// catch 404 and forward to error handle
app.use((req, res, next) => {
next('not found');
});
The end file should looks something like this now if you use this approach:
...
... all previous routes
...
// catch 404 and forward to error handle
app.use((req, res, next) => {
next('not found');
});
// handle all other error
app.use(errorHandler);
Hope this helps
Related
app.get('/test', (req, res, next) => {
const err = new Error('Test');
next(err);
});
express will log the error and stacktrace to the console. Is there a way that I can suppress the logging?
If you put an error handler middleware in your Express implementation to handle the next(err) call like this:
// defined as the last route
app.use(function (err, req, res, next) {
res.status(500).send('Something broke!')
});
then, Express won't log any error and you can control what response is sent for the error.
I'd suggest reading this page on Express error handling: https://expressjs.com/en/guide/error-handling.html.
If you look in the express code where this logging comes from, it comes from this code:
function logerror(err) {
/* istanbul ignore next */
if (this.get('env') !== 'test') console.error(err.stack || err.toString());
}
which curiously enough shows that if you do this:
app.set('env', 'test');
or you set NODE_ENV=test in the environment before launching your server, then it will skip the error logging too. But, it's much better to just control things with your own error handler as I show about where you can also control what type of response is sent.
Following a generated template for express. In the app.js there is a following snippet
app.use('/users', users);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
Per my understanding, the middleware will run in order from app.use('/users', users) to 404 handler to error handler. My question is how will it be possible to reach error handler and execute this line res.status(err.status || 500); ? Wont every failed request be passed through 404 handler first and therefor getting the status code of 404? Please let me know if I am missing something! Thank you
No, it won't. If you look at these event handlers declarations you'll see that error handler for unhandled error, has an additional err parameter:
app.use(function(req, res, next) {
app.use(function(err, req, res, next) {
Error-handling middleware always takes four arguments. You must provide four arguments to identify it as an error-handling middleware function. Even if you don’t need to use the next object, you must specify it to maintain the signature. Otherwise, the next object will be interpreted as regular middleware and will fail to handle errors. For details about error-handling middleware.
So, when the route is not found, the last declared middleware is calling, it's 404 error handler.
But when you call next with error: next(err) or your code throws an error, the last error handler is calling.
System error should be handled before 404
app.use('/users', users);
// error handler
app.use(function(err, req, res, next) {
// No routes handled the request and no system error, that means 404 issue.
// Forward to next middleware to handle it.
if (!err) return next();
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
// catch 404. 404 should be consider as a default behavior, not a system error.
app.use(function(req, res, next) {
res.status(404);
res.render('Not Found');
});
Wont every failed request be passed through 404 handler first and therefor getting the status code of 404?
No, the 404 route is just a standard middleware and usually wired up last, meaning if no other routes handle the request they will eventually hit the 404.
The 500 is a special type of middleware, you'll notice it has 4 parameters (first one being an error param). This middleware is invoked as soon as you call next with an error, or any sort of data for that matter.
See the docs
Something to note for anyone else that stumbles around this post.
Your last middleware to be invoked (if none gets invoked) - will be 404, it doesn't take an err as its middleware parameter. Hence, you can't/shouldn't try to catch the 404 before any other middleware. So your last middleware should be without the err parameter like the example below and of course, should be at the bottom of your application stack.
//at the botton of your app.js
// catch 404.
//no err parameter
app.use(function(req, res, next) {
res.render("404Page"{
url: req.url
});
});
If you want to catch your errors before it goes to this handler use a (err, req, res, next) before this middleware.
app.use(function (req, res, next) {
throw new Error('critical');
})
makes Express server to catch a critical error and output it, while I want it to crash.
Adding an error handler doesn't replace the default handler.
How can Express error handling be disabled for critical errors?
If you want your server to crash in the event of a critical error, you can define an error-handling middleware. This is done by defining a function with 4 parameters, the first being the error. This will be called when an error is thrown. You can check the error and determine if it's critical, and if so, call process.exit.
const app = require('express')()
app.use('/', (req, res) => {
throw new Error('critical')
})
app.use((err, req, res, next) => {
if (err.message === 'critical') {
process.exit(1)
} else {
// carry on listening
}
})
While coding my app, I sometimes had a little mishap when typing my URLs in the browser, and thus sometimes got the error message:
Cannot GET /some/route
Which was true, since the route may was not defined.
But since this app is planned to enter production, I kinda don't want to use this flat message as my "error page".
Looking into the Express 4 docs, they tell me to .use() a middleware with 4 arguments. I did that. But I'd still get this issue...
Turns out that this message comes from the finalhandler module and my bet is, that this middleware comes before my error-catching, 4-argument middleware.
Here is a basic express app that I threw together while trying to find a solution:
var app = require("express")();
app.use("/yo", function(req, res, next){
res.send("Yo!");
});
app.use(function(error, req, res, next){
res.send("An error: "+error);
console.log(error);
});
app.listen(10000);
Accessing /yo works. But, / or /derp yields the Cannot GET message instead of my little middleware.
So, how is this done correctly, now?
The error middleware is only for actual errors, such as a middleware or route handler throwing an exception or passing an error to next().
If you want to provide a route handler for requests that do not match any existing routes, then just add a middleware after all of your app's routes/middleware like:
var app = require("express")();
app.use("/yo", function(req, res, next){
res.send("Yo!");
});
app.use(function(req, res, next) {
res.send('Could not route your request!');
});
app.use(function(error, req, res, next){
res.send("An error: "+error);
console.log(error);
});
app.listen(10000);
My express app uses the default JSON body parser:
app.use(bodyParser.json());
Further down the app.js file I have my own router for building REST API routes:
var api = require('./routes/api/index');
...
app.use('/api', api);
This router has an error handler, among other things:
router.use(function (err, req, res, next) {
debugger;
res.status(err.code ? getParseErrorStatus(err.code) : res.status || 500).send({
error: err.message
});
});
Whenever the bodyParser throws an error while parsing the request body, I get my generic express error handler called:
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
app.use(function (err, req, res, next) {
debugger;
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
}
This holds true even for calls to /api/* URLs that are caught by the API router.
Instead of always calling the generic error handler, how can I make JSON parsing errors (caught by middleware up in the chain) call the error handler I've defined in the API router, if the URL is an API URL?
Error handlers are called at the same router stack level. Since the bodyParser.json() is executed at the main/root/app layer, it will look for the first error handler at the main/root/app layer.
So if you want to handle API body parsing errors, you will have to move your bodyParser.json() middleware to each of your routes/routers that require body parsing.