├── .gitignore ├── .travis.yml ├── README.md ├── index.js ├── package.json └── test ├── app.js ├── index.js └── mocha.opts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # express-domain-middleware 2 | 3 | [![Build Status](https://travis-ci.org/brianc/node-domain-middleware.png?branch=master)](https://travis-ci.org/brianc/node-domain-middleware) 4 | 5 | Hi! Are you using `process.uncaughtException` in your express apps to keep them running? 6 | 7 | OR...Do you just let your process crash on any unhandled exception and restart it? 8 | 9 | Do you find it hard to pass "request.id" to 8 nested database calls so you can keep a context of what request you're working on? 10 | 11 | How do you associate log entries with a specific request? Passing the request around everywhere again? 12 | 13 | Domains can help. 14 | 15 | ## USE DOMAINS 16 | 17 | First, [read this](http://nodejs.org/api/domain.html) 18 | 19 | Second, realize once you enable domains `process.domain` will give you the active domain. 20 | 21 | Third, use this middleware to bind each request/response pair to its own domain. 22 | 23 | ```js 24 | express.use(require('express-domain-middleware')); 25 | ``` 26 | 27 | ### express-domain-middleware api 28 | 29 | #### var domainMiddleware = require('express-domain-middleware'); 30 | 31 | #### domainMiddleware = function(req, res, next) 32 | 33 | Exports a function matching the signature of express middleware. Binds incoming request & response from express to a new domain. Assigns the domain a unique id by calling `domainMiddleware.id(req)`. 34 | 35 | If the domain emits an error event, `domainMiddleware` will call `next(err)` with the error event from the domain. This allows existing express specific error handling middleware to 36 | function as if the error was hanlded by your application code. Allow me to demonstrate with an example: 37 | 38 | ```js 39 | ///old-school 40 | app.get('/error', function(req, res, next) { 41 | db.query('SELECT happiness()', function(err, rows) { 42 | if(err) return next(err); 43 | fs.readFile('alskdjflasd', function(err, contents) { 44 | if(err) return next(err); 45 | process.nextTick(function() { 46 | throw new Error("congratulations, your node process crashed and the user request disconnected in a jarring way"); 47 | }); 48 | }); 49 | }) 50 | }); 51 | ``` 52 | 53 | 54 | now with less crashing... 55 | 56 | 57 | ```js 58 | //with domain-middleware 59 | app.use(require('express-domain-middleware')); 60 | app.use(app.router); 61 | app.use(function errorHandler(err, req, res, next) { 62 | console.log('error on request %d %s %s', process.domain.id, req.method, req.url); 63 | console.log(err.stack); 64 | res.send(500, "Something bad happened. :("); 65 | if(err.domain) { 66 | //you should think about gracefully stopping & respawning your server 67 | //since an unhandled error might put your application into an unknown state 68 | } 69 | }); 70 | app.get('/error', function(req, res, next) { 71 | db.query('SELECT happiness()', process.domain.intercept(function(rows) { 72 | fs.readFile('asldkfjasdf', process.domain.intercept(function(contents) { 73 | process.nextTick(process.domain.intercept(function() { 74 | throw new Error("The individual request will be passed to the express error handler, and your application will keep running."); 75 | })); 76 | })); 77 | })); 78 | }); 79 | ``` 80 | 81 | 82 | I have to recommend using [okay](https://github.com/brianc/node-okay) to gracefully fallback in the absence of domains. Plus..it's terse. Go, code golf! 83 | 84 | ```js 85 | var ok = require('okay'); 86 | app.use(require('express-domain-middleware')); 87 | app.use(app.router); 88 | app.use(function errorHandler(err, req, res, next) { 89 | console.log('error on request %d %s %s: %j', process.domain.id, req.method, req.url, err); 90 | res.send(500, "Something bad happened. :("); 91 | }); 92 | app.get('/error', function(req, res, next) { 93 | db.query('SELECT happiness()', ok(next, function(rows) { 94 | fs.readFile('asldkfjasdf', ok(next, function(contents) { 95 | process.nextTick(ok(next, function() { 96 | throw new Error("The individual request will be passed to the express error handler, and your application will keep running."); 97 | })); 98 | })); 99 | })); 100 | }); 101 | ``` 102 | 103 | ## license 104 | MIT 105 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var createDomain = require('domain').create 2 | 3 | var domainMiddleware = module.exports = function(req, res, next) { 4 | var domain = createDomain(); 5 | domain.id = domainMiddleware.id(req); 6 | domain.add(req); 7 | domain.add(res); 8 | domain.run(next); 9 | domain.on('error', next); 10 | }; 11 | 12 | var count = 0; 13 | //you can replace this method to 14 | //supply your own id builder 15 | domainMiddleware.id = function(req) { 16 | return new Date().getTime() + (count++); 17 | }; 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-domain-middleware", 3 | "version": "0.1.0", 4 | "description": "wrap express request/response with node domains", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "mocha" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/brianc/node-domain-middleware" 15 | }, 16 | "keywords": [ 17 | "domain", 18 | "express", 19 | "middleware" 20 | ], 21 | "author": "Brian M. Carlson", 22 | "license": "MIT", 23 | "devDependencies": { 24 | "omf": "1.2.0", 25 | "okay": "0.2.0", 26 | "mocha": "1.8.2", 27 | "express": "3.2.4" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | 3 | var domainMiddleware = require(__dirname + '/../'); 4 | 5 | var app = module.exports = express(); 6 | 7 | app.use(domainMiddleware); 8 | app.use(app.router); 9 | //error-handling middleware 10 | app.use(function(err, req, res, next) { 11 | res.json(500, { id: process.domain.id }); 12 | }); 13 | 14 | app.get("/", function(req, res, next) { 15 | res.json({ id: process.domain.id }); 16 | }); 17 | 18 | app.get("/async", function(req, res, next) { 19 | var fs = require('fs'); 20 | var ok = require('okay'); 21 | process.nextTick(ok(function() { 22 | fs.readFile(__filename, 'utf8', function(contents) { 23 | res.json({ id: process.domain.id }); 24 | }); 25 | })); 26 | }); 27 | 28 | app.get('/closed-error', function(req, res, next) { 29 | var fs = require('fs'); 30 | res.writeHead(200, {'content-type':'application/json'}); 31 | setTimeout(function() { 32 | fs.readFile(__filename, function() { 33 | res.end(JSON.stringify({id: process.domain.id})); 34 | throw new Error('why are you coding like this?'); 35 | }); 36 | }, 10); 37 | }); 38 | 39 | app.get('/timeout-error', function(req, res, next) { 40 | setTimeout(function() { 41 | throw new Error('BOOM'); 42 | }, 10); 43 | }); 44 | 45 | app.get('/file-error', function(req, res, next) { 46 | var fs = require('fs'); 47 | fs.readFile('aslkdjflasdfl', function(err, contents) { 48 | if(err) throw err; 49 | }); 50 | }); 51 | 52 | if(!module.parent) { 53 | var http = require('http'); 54 | var server = http.createServer(app); 55 | var port = process.argv[2] || 3000; 56 | server.listen(port, function() { 57 | console.log('test app listening on %d', port); 58 | var routes = ['/closed-error', '/timeout-error', '/file-error', '/']; 59 | console.log('try hitting the following in your browser:'); 60 | routes.forEach(function(route) { 61 | console.log('\thttp://localhost:%d' + route, port); 62 | }) 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | var omf = require('omf'); 4 | 5 | var app = require(__dirname + '/app'); 6 | 7 | var isValidResponse = function(statusCode) { 8 | return function(res) { 9 | res.has.statusCode(statusCode); 10 | res.is.json(); 11 | it('has id in body', function() { 12 | var body = JSON.parse(this.response.body); 13 | assert(body.id); 14 | assert.equal(typeof body.id, 'number'); 15 | }); 16 | }; 17 | }; 18 | 19 | omf(app, function(app) { 20 | app.get('/', isValidResponse(200)); 21 | app.get('/async', isValidResponse(200)); 22 | app.get('/timeout-error', isValidResponse(500)); 23 | app.get('/file-error', isValidResponse(500)); 24 | app.get('/closed-error', isValidResponse(200)); 25 | app.get('/', isValidResponse(200)); 26 | }); 27 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --bail 2 | --------------------------------------------------------------------------------