Error Handling
Error Handling refers to how Express catches and processes errors thatoccur both synchronously and asynchronously. Express comes with a default errorhandler so you don’t need to write your own to get started.
Catching Errors
It’s important to ensure that Express catches all errors that occur whilerunning route handlers and middleware.
Errors that occur in synchronous code inside route handlers and middlewarerequire no extra work. If synchronous code throws an error, then Express willcatch and process it. For example:
app.get('/',(req,res)=>{thrownewError('BROKEN')// Express will catch this on its own.})For errors returned from asynchronous functions invoked by route handlersand middleware, you must pass them to thenext() function, where Express willcatch and process them. For example:
app.get('/',(req,res,next)=>{fs.readFile('/file-does-not-exist',(err,data)=>{if(err){next(err)// Pass errors to Express.}else{res.send(data)}})})Starting with Express 5, route handlers and middleware that return a Promisewill callnext(value) automatically when they reject or throw an error.For example:
app.get('/user/:id',async(req,res,next)=>{constuser=awaitgetUserById(req.params.id)res.send(user)})IfgetUserById throws an error or rejects,next will be called with eitherthe thrown error or the rejected value. If no rejected value is provided,nextwill be called with a default Error object provided by the Express router.
If you pass anything to thenext() function (except the string'route'),Express regards the current request as being an error and will skip anyremaining non-error handling routing and middleware functions.
If the callback in a sequence provides no data, only errors, you can simplifythis code as follows:
app.get('/',[function(req,res,next){fs.writeFile('/inaccessible-path','data',next)},function(req,res){res.send('OK')}])In the above example,next is provided as the callback forfs.writeFile,which is called with or without errors. If there is no error, the secondhandler is executed, otherwise Express catches and processes the error.
You must catch errors that occur in asynchronous code invoked by route handlers ormiddleware and pass them to Express for processing. For example:
app.get('/',(req,res,next)=>{setTimeout(()=>{try{thrownewError('BROKEN')}catch(err){next(err)}},100)})The above example uses atry...catch block to catch errors in theasynchronous code and pass them to Express. If thetry...catchblock were omitted, Express would not catch the error since it is not part of the synchronoushandler code.
Use promises to avoid the overhead of thetry...catch block or when using functionsthat return promises. For example:
app.get('/',(req,res,next)=>{Promise.resolve().then(()=>{thrownewError('BROKEN')}).catch(next)// Errors will be passed to Express.})Since promises automatically catch both synchronous errors and rejected promises,you can simply providenext as the final catch handler and Express will catch errors,because the catch handler is given the error as the first argument.
You could also use a chain of handlers to rely on synchronous errorcatching, by reducing the asynchronous code to something trivial. For example:
app.get('/',[function(req,res,next){fs.readFile('/maybe-valid-file','utf-8',(err,data)=>{res.locals.data=datanext(err)})},function(req,res){res.locals.data=res.locals.data.split(',')[1]res.send(res.locals.data)}])The above example has a couple of trivial statements from thereadFilecall. IfreadFile causes an error, then it passes the error to Express, otherwise youquickly return to the world of synchronous error handling in the next handlerin the chain. Then, the example above tries to process the data. If this fails, then thesynchronous error handler will catch it. If you had done this processing insidethereadFile callback, then the application might exit and the Express errorhandlers would not run.
Whichever method you use, if you want Express error handlers to be called in and theapplication to survive, you must ensure that Express receives the error.
The default error handler
Express comes with a built-in error handler that takes care of any errors that might be encountered in the app. This default error-handling middleware function is added at the end of the middleware function stack.
If you pass an error tonext() and you do not handle it in a custom errorhandler, it will be handled by the built-in error handler; the error will bewritten to the client with the stack trace. The stack trace is not includedin the production environment.
Set the environment variableNODE_ENV toproduction, to run the app in production mode.
When an error is written, the following information is added to theresponse:
- The
res.statusCodeis set fromerr.status(orerr.statusCode). Ifthis value is outside the 4xx or 5xx range, it will be set to 500. - The
res.statusMessageis set according to the status code. - The body will be the HTML of the status code message when in productionenvironment, otherwise will be
err.stack. - Any headers specified in an
err.headersobject.
If you callnext() with an error after you have started writing theresponse (for example, if you encounter an error while streaming theresponse to the client), the Express default error handler closes theconnection and fails the request.
So when you add a custom error handler, you must delegate tothe default Express error handler, when the headershave already been sent to the client:
functionerrorHandler(err,req,res,next){if(res.headersSent){returnnext(err)}res.status(500)res.render('error',{error:err})}Note that the default error handler can get triggered if you callnext() with an errorin your code more than once, even if custom error handling middleware is in place.
Other error handling middleware can be found atExpress middleware.
Writing error handlers
Define error-handling middleware functions in the same way as other middleware functions,except error-handling functions have four arguments instead of three:(err, req, res, next). For example:
app.use((err,req,res,next)=>{console.error(err.stack)res.status(500).send('Something broke!')})You define error-handling middleware last, after otherapp.use() and routes calls; for example:
constbodyParser=require('body-parser')constmethodOverride=require('method-override')app.use(bodyParser.urlencoded({extended:true}))app.use(bodyParser.json())app.use(methodOverride())app.use((err,req,res,next)=>{// logic})Responses from within a middleware function can be in any format, such as an HTML error page, a simple message, or a JSON string.
For organizational (and higher-level framework) purposes, you can defineseveral error-handling middleware functions, much as you would withregular middleware functions. For example, to define an error-handlerfor requests made by usingXHR and those without:
constbodyParser=require('body-parser')constmethodOverride=require('method-override')app.use(bodyParser.urlencoded({extended:true}))app.use(bodyParser.json())app.use(methodOverride())app.use(logErrors)app.use(clientErrorHandler)app.use(errorHandler)In this example, the genericlogErrors might write request anderror information tostderr, for example:
functionlogErrors(err,req,res,next){console.error(err.stack)next(err)}Also in this example,clientErrorHandler is defined as follows; in this case, the error is explicitly passed along to the next one.
Notice that whennot calling “next” in an error-handling function, you are responsible for writing (and ending) the response. Otherwise, those requests will “hang” and will not be eligible for garbage collection.
functionclientErrorHandler(err,req,res,next){if(req.xhr){res.status(500).send({error:'Something failed!'})}else{next(err)}}Implement the “catch-all”errorHandler function as follows (for example):
functionerrorHandler(err,req,res,next){res.status(500)res.render('error',{error:err})}If you have a route handler with multiple callback functions, you can use theroute parameter to skip to the next route handler. For example:
app.get('/a_route_behind_paywall',(req,res,next)=>{if(!req.user.hasPaid){// continue handling this requestnext('route')}else{next()}},(req,res,next)=>{PaidContent.find((err,doc)=>{if(err)returnnext(err)res.json(doc)})})In this example, thegetPaidContent handler will be skipped but any remaining handlers inapp for/a_route_behind_paywall would continue to be executed.
Calls tonext() andnext(err) indicate that the current handler is complete and in what state.next(err) will skip all remaining handlers in the chain except for those that are set up to handle errors as described above.