Because Node.js allows developers to write asynchronous code, and that’s what we usually do, and because state changes during different async parts of code, sometimes it’s harder to trace errors and have a meaningful state and context in which the application was during that exception.

Take a look at this synchronous error code in Node:

try {
  throw new Error('Fail!')
} catch (e) {
  console.log('Custom Error: ' + e.message)
}

For sync errors try/catch works fine. Now take a look at this asynchronous error code example:

try {
  setTimeout(function () {
    throw new Error('Fail!')
  }, Math.round(Math.random()*100))
} catch (e) {
  console.log('Custom Error: ' + e.message)
}

The app crashes!

To mitigate this, we have domains in Node.js.

Contrary to its more popular homonym (domain as in Webapplog.com or Node.University), domain is a core Node.js module. It aids developers in tracking and isolating errors that could be a juggernaut task. Think of domains as a smarter version of try/catch statements.

Warning The domain module is in the deprecated stage, which means that it’s likely to be removed from core… but it will live in some other npm module and you can use it. I don’t recommend using Domains in production but it’s a good exercise to see how they function and how they are implemented.

Here’s the Domain example which catches the thrown error just fine:

let domain = require('domain').create()
domain.on('error', function(error){
  console.log(error)
})
domain.run(function(){
  throw new Error('Failed!')
})

What about async errors? It works fine too. Here’s the Domain with async error code:

let d = require('domain').create()
d.on('error', function(e) {
   console.log('Custom Error: ' + e)
})
d.run(function() {
  setTimeout(function () {
    throw new Error('Failed!')
  }, Math.round(Math.random()*100))
})

Domain code works fine with async error. Yay.

When it comes to Express.js (and other frameworks), we can apply domains in error-prone routes. A route can become error prone if it has pretty much any nontrivial code (i.e., any route can be prone to error), but usually developers can just analyze logs and determine which URL and path are causing the crashes. Typically, these routes rely on third-party modules, some communication, or file system/database input/output.

Before defining the routes, we need to define custom handlers to catch errors from domains. In Express.js we do the following:

const express = require('express')
const domain = require('domain')
const defaultHandler = require('errorhandler')

Then, we add middleware using ES6 fat arrow function. The middleware sends an error to domain or processes it:

app.use((error, req, res, next) => {
  if (domain.active) {
    console.info('caught with domain')
    domain.active.emit("error", error);
  } else {
    console.info('no domain')
    defaultHandler(error, req, res, next)
  }
})

Here is a “crashy route” in which the error-prone code goes inside the d.run callback:

app.get('/e', (req, res, next) => {
  const d = domain.create()
  d.on('error', (error) => {
    console.error(error.stack)
    res.send(500, {'error': error.message})
  })
  d.run(() => {
    // Error-prone code goes here
    throw new Error('Database is down.') // Like a real crash
  })
})

On the other hand, we can call next with an error object (e.g., when an error variable comes from other nested calls):

app.get('/e', (req, res, next) => {
  var d = domain.create()
  d.on('error', (error) => {
    console.error(error.stack)
    res.send(500, {'error': error.message})
  })
  d.run(() => {
    // Error-prone code goes here
    next(new Error('Database is down.'))
  })
})

After you launch this example with $ node app, go to the /e URL. You should see the following information in your logs:

caught with domain { domain: null,
  _events: { error: [Function] },
  _maxListeners: 10,
    members: [] }
Error: Database is down.
    at /Users/azat/Documents/Code/practicalnode/ch10/domains/app.js:29:10
    at b (domain.js:183:18)
    at Domain.run (domain.js:123:23)

The stack trace information (lines after Error: Database is down.) might be very handy in debugging async code. And the browser should output a nice JSON error message:

{"error":"Database is down."}

The working (or should we write crashing) example of Express.js 4.1.2 and domains in routes is in the ch10/domains folder on GitHub.

The package.json for this example looks like this:

{
  "name": "express-domains",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "4.1.2",
    "pug": "",
    "errorhandler": "1.0.1"
  }
}

For your convenience, here’s the full content of practicalnode/ch10/domains/app.js:

var express = require('express');
var routes = require('./routes');
var http = require('http');
var path = require('path');
var errorHandler = require('errorhandler');

var app = express();

app.set('port', process.env.PORT || 3000);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.static(path.join(__dirname, 'public')));

var domain = require('domain');
var defaultHandler = errorHandler();
app.get('/', routes.index);

app.get('/e', function (req, res, next) {
  var d = domain.create();
  d.on('error', function (error) {
    console.error(error.stack);
    res.send(500, {'error': error.message});
  });
  d.run(function () {
    // Error-prone code goes here
    throw new Error('Database is down.');
    // next(new Error('Database is down.'));
  });
});

app.use(function (error, req, res, next) {
  if (domain.active) {
    console.info('caught with domain', domain.active);
    domain.active.emit('error', error);
  } else {
    console.info('no domain');
    defaultHandler(error, req, res, next);
  }
});

http.createServer(app).listen(app.get('port'), function () {
  console.log('Express server listening on port ' 
    + app.get('port'));
});

For more ways to apply domains with Express.js, take a look at the presentation from NodeConf 2013 slide 4–1 (http://othiym23.github.io/nodeconf2013-domains/#/4/1).

For more Node.js tutorials like this, take a look at Node University at https://node.university.