For certain pages I have custom 500, 404 and 403 error handling in my app. So for instance after an unsuccessful database query I'd go:
return next({status: 404, message: 'Record not found'});
or
return next(new Error('Bad things have happened')});
In my middleware I have an error handler:
app.use(function (err, req, res, next) { // handle error });
Problem is that the error handler is never called, instead the error callstack is being printed into the browser. I want the handler to render a custom error page.
app.js
var express = require('express') , app = express() , swig = require('swig') , config = require('./lib/config') , env = process.env.NODE_ENV || 'development' , path = require('path'); config.configure(env); app.engine('html', swig.renderFile); app.set('view cache', false); swig.setDefaults({ cache: config.get('swigCache') }); app.set('view engine', 'html'); app.set('views', __dirname + '/lib/views'); require('./lib/util/swig'); require('./lib/initialisers/mongodb')(); require('./lib/initialisers/aws')(); require('./lib/middleware')(app); // first load middleware require('./lib/routes')(app); // then routes var server = app.listen(config.get('port'), function() { console.info('config: ' + JSON.stringify(config.getCurrent())); console.info('NODE_ENV: ' + env); console.info('server running: ' + JSON.stringify(server.address())); });
routes.js
module.exports = function(app){ app.get('/', require('./views/').index); app.get('/blog', require('./views/blog').index); app.get('/blog/:slug', require('./views/blog').getBySlug); app.route('/report/:slug') .get(require('./views/report/').index) .post(require('./views/report/').doReport); // Very long file with tons of routes. Simplified version.
middleware.js
var express = require('express') , app = express() , path = require('path') , logger = require('morgan') , cookieParser = require('cookie-parser') , bodyParser = require('body-parser') , passport = require('passport') , session = require('express-session') , mongoStore = require('connect-mongo')(session) , compression = require('compression') , favicon = require('serve-favicon') , config = require('./config') , flash = require('connect-flash') , multer = require('multer') , csrf = require('csurf'); module.exports = function(app) { app.use(bodyParser.urlencoded({ extended: false })) app.use(bodyParser.json()); app.use(cookieParser()); app.use(csrf({ cookie: true })); app.use(express.static(path.join(__dirname, config.get('staticContentPath')), { maxAge: (60 * 60 * 24) * 1000 })); app.use(session({ resave: true, saveUninitialized: true, secret: 'da755fc0-6882-11e4-9803-0800200c9a66', cookie: { maxAge: 24 * 60 * 60 * 1000 // 24 hrs }, store: new mongoStore({ url: config.getMongoConn() }) })); app.use(logger('dev')); app.use(flash()); /** * 301 redirects */ app.use(function(req, res, next) { var host = req.get('host'); // AWS IP --> http if (host == 'xx.xxx.xxx.xxx') { return res.redirect(301, config.get('url') + req.originalUrl); } // AWS origin --> http if(host == 'xxx-xxx-xxx-xxx-xxx.ap-southeast-2.compute.amazonaws.com'){ return res.redirect(301, config.get('url') + req.originalUrl); } // www --> http if (/^www\./.test(host)) { host = host.substring(4, host.length); return res.redirect(301, req.protocol + '://' + host + req.originalUrl); } // Trailing slash --> http if (req.path.substr(-1) == '/' && req.path.length > 1) { var query = req.url.slice(req.path.length); return res.redirect(301, req.path.slice(0, -1) + query); } next(); }); // Delete expired Mongo sessions from DB app.use(function (req, res, next) { req.session._garbage = new Date(); req.session.touch(); next(); }); /** * Setting Cache control header for Ajax requests to 30 minutes */ app.use(function (req, res, next) { if(req.xhr){ res.header('Cache-Control', 'max-age=' + 1800 + ', public'); } next(); }); app.use(compression()); app.use( multer({ dest: config.get('uploads').folders.temp }) ); app.use(passport.initialize()); app.use(passport.session()); var initPassport = require('./passport/init'); initPassport(passport); app.use(function (req, res, next) { res.locals = { root : 'http://' + req.headers.host, sitename : require('./config').get('sitename'), config: config.get('env'), url : config.get('url'), user : req.user, flash : req.flash() }; next(); }); app.use(function (err, req, res, next) { if (err.code !== 'EBADCSRFTOKEN'){ return next(err); } if(req.xhr){ return res.ok({payload: null}, '403 invalid csrf token'); } // TODO handle CSRF token errors here res.status(403); res.send('form tampered with') }); // This is never called when throwing errors like // next(new Error('some error') or // next({status: 500, message:'server error'}); app.use(function (err, req, res, next) { console.error(err.stack); // render an error page }); };
The problem is, that your error handlers should always be at the end of your application stack. This means, that you can either move the error handler from your middleware to your app.js and use them after your requires (app.use()) or include your routes before your middleware.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With