Error handling principles for Node.js + Express.js applications?

node.jsExpress

node.js Problem Overview


It seems like error reporting/handling is done differently in Node.js+Express.js applications compared to other frameworks. Am I correct in understanding that it works as follows?

A) Detect errors by receiving them as parameters to your callback functions. For example:

doSomethingAndRunCallback(function(err) { 
    if(err) { … }
});

B) Report errors in MIDDLEWARE by calling next(err). Example:

handleRequest(req, res, next) {
    // An error occurs…
    next(err);
}

C) Report errors in ROUTES by throwing the error. Example:

app.get('/home', function(req, res) {
    // An error occurs
    throw err;
});

D) Handle errors by configuring your own error handler via app.error() or use the generic Connect error handler. Example:

app.error(function(err, req, res, next) {
    console.error(err);
    res.send('Fail Whale, yo.');
});

Are these four principles the basis for all error handling/reporting in Node.js+Express.js applications?

node.js Solutions


Solution 1 - node.js

Error handling in Node.js is generally of the format A). Most callbacks return an error object as the first argument or null.

Express.js uses middleware and the middleware syntax uses B) and E) (mentioned below).

C) is bad practice if you ask me.

app.get('/home', function(req, res) {
    // An error occurs
    throw err;
});

You can easily rewrite the above as

app.get('/home', function(req, res, next) {
    // An error occurs
    next(err);
});

Middleware syntax is valid in a get request.

As for D)

> (07:26:37 PM) tjholowaychuk: app.error is removed in 3.x

TJ just confirmed that app.error is deprecated in favor of E

E)

app.use(function(err, req, res, next) {
  // Only handle `next(err)` calls
});

Any middleware that has a length of 4 (4 arguments) is considered error middleware. When one calls next(err) connect goes and calls error-based middleware.

Solution 2 - node.js

People at Joyent have published a really insightful best-practices document on this. A must-read article for any Node.js developer.

Solution 3 - node.js

Why first-parameter?

Because of the asynchronous nature of Node.js, the first-parameter-as-err pattern has become well established as a convention for userland Node.js error handling. This is because asynchronous:

try {
    setTimeout(function() {
        throw 'something broke' //Some random error
    }, 5)
}
catch(e) {
   //Will never get caught
}

So instead having the first argument of the callback is pretty much the only sensible way to pass errors asynchronously other than just throwing them.

To do so will result in an unhandled exception which, just in the way it sounds, implies that nothing was done to get the application out of its confused state.

Exceptions, why do they exist

It is worth noting however, that virtually all part of Node.js are event-emitters and the throwing of an exception is a low-level event which can be handled like all events:

//This won't immediately crash if connection fails
var socket = require("net").createConnection(5000);
socket.on("error", function(err) {
    console.error("calm down...", err)
});

This can-but-shouldn't be taken to the extreme to catch all errors and make an application which will try very hard to never crash. This is a terrible idea in nearly every use-case, because it will leave the developer without any idea of what's going on in the application state and is analogous to wrapping main in try-catch.

Domains - grouping events logically

As part of dealing with this problem of exceptions making applications fall over, domains allow the developer to take, for example the Express.js application, and try and close off connections sensibly in the event of catastrophic failure.

ES6

It's probably mentioning that this will change again as ES6 allows the generator pattern to create asynchronous events which are still catchable with try/catch blocks.

Koa (written by TJ Holowaychuck, same original author of Express.js) noticeably does this. It uses the ES6 yield statement to create blocks that, while appearing nearly syncronous, are handled in the usual node asynchronous fashion:

app.use(function *(next) {
    try {
        yield next;
    } 
    catch (err) {
        this.status = err.status || 500;
        this.body = err.message;
        this.app.emit('error', err, this);
    }
});

app.use(function *(next) {
    throw new Error('some error');
})

This example was shamelessly stolen from here.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionClint HarrisView Question on Stackoverflow
Solution 1 - node.jsRaynosView Answer on Stackoverflow
Solution 2 - node.jsuser2030471View Answer on Stackoverflow
Solution 3 - node.jsDavidView Answer on Stackoverflow