181

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?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
clint
  • 14,402
  • 12
  • 70
  • 79

3 Answers3

186

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Raynos
  • 166,823
  • 56
  • 351
  • 396
  • 11
    Thanks! For anyone who might come across this in the future, it looks like the order of params for "method e" is actually err, req, res, next (instead of req, res, next, err). – clint Aug 22 '11 at 22:30
  • @ClintHarris you are correct, I've changed it. It makes more sense that way – Raynos Aug 22 '11 at 22:41
  • 9
    So this looks great, but a problem I'm seeing is that some errors never make their way to the error handlers you describe, and can only be caught by a process.on('uncaughtException', fn) handler. The conventional wisdom is to let that happen and rely on Forever or the like to restart the app, but if you do that, how do you return a friendly error page? – Paul Apr 18 '12 at 17:29
  • i tried E but my app still crashed. It was from a user.save() operation that had a duplciate key. It was supposed to render error.ejs – chovy Sep 23 '12 at 08:38
  • @chovy express doesn't handle all possible asynchronous errors for you – Raynos Sep 23 '12 at 14:14
  • @Raynos - do you know what's the accepted approach for dealing with these async errors? – UpTheCreek Oct 30 '12 at 12:56
  • @UpTheCreek you handle them. Generally you use domains and send a 500 to the client – Raynos Nov 02 '12 at 03:46
  • 1
    @chovy Also, just an fyi. The error handler has to be given to the app *after* the thrown/next'd error. If it is before, it will not catch the error. – Lee Olayvar Dec 07 '12 at 21:25
  • 3
    next(err) is essentially Express's version of throwing an error, you have to explicitly call it within your own middleware though – qodeninja Jan 28 '14 at 20:25
  • 1
    @qodeninja That method is considered a best practice in Express. – David Oliveros Feb 11 '14 at 20:03
  • @Paul Nicely said, although there's nothing wrong against using process.on('UncaughtException', fn) as long as you restart the app. The problem is that you can not respond to the request because UncaughtException happens on a lower level, outside of the request's scope. In case the errors "never make their way to the error handlers" truly happens, you would've no way of returning anything anyways. – David Oliveros Feb 11 '14 at 20:03
  • In E) there it reads `// only handle next(err) calls`. Is that true? In may application this handler is reached by some Exceptions inside middleware (eg `throw`) too. – dronus Mar 03 '14 at 12:32
  • 1
    what i still couldn't figure out is, what is the format of the error ? I tried next({type: '404'}), doesn't seem working – windmaomao Dec 31 '14 at 16:09
11

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

stephbu
  • 5,072
  • 26
  • 42
3

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
David
  • 964
  • 7
  • 14