title: Pass DB context between Node.js module functions
body: <p>I'm running Express on Node.js and am wondering how I can effectively pass a single database connection context object between distinct Node modules (think of them sort of like application models).</p>
  
  <p>I'd like to do this to be able to start a database transaction in one model and preserve it across calls to other affected models, for the duration of a single HTTP request.</p>
  
  <p>I've seen people attempt to solve this using per-request database connections exposed as middleware before my route is run (taking from a connection pool, then running another piece of middleware after the routes to return the connection to the pool). That unfortunately means explicitly passing around a context object to all the affected functions, which is inelegant and clunky.</p>
  
  <p>I've also seen people talking about the <a href="https://github.com/othiym23/node-continuation-local-storage" rel="nofollow">continuation-local-storage</a> and <a href="https://github.com/nodejs/diagnostics/tree/master/tracing/AsyncWrap" rel="nofollow">AsyncWrap</a> modules, but I'm unclear how they can solve my particular problem. I tried working with continuation-local-storage briefly but because I primarily use promises and generators in my code, it wasn't able to pass state back from the <code>run</code> method (it simply returns the <code>context</code> object passed into its callback).</p>
  
  <p>Here's an example of what I'm trying to do:</p>
  
  <pre><code>// player-routes.js
  router.post('/items/upgrade', wrap(function *(req, res) {
    const result = yield playerItem.upgrade(req.body.itemId);
  
    res.json();
  });
  
  // player-item.js
  const playerItem = {
    upgrade: Promise.coroutine(function *(user, itemId) {
      return db.withTransaction(function *(conn) {
        yield db.queryAsync('UPDATE player_items SET level = level + 1 WHERE id = ?', [itemId]);
        yield player.update(user);
  
        return true;
      });
    })
  };
  
  module.exports = playerItem;
  
  // player.js
  const player = {
    update(user) {
      return db.queryAsync('UPDATE players SET last_updated_at = NOW() WHERE id = ?', [user.id]);
    })
  };
  
  module.exports = player;
  
  // db.js
  db.withTransaction = function(genFn) {
    return Promise.using(getTransactionConnection(), conn =&gt; {
      return conn.beginTransactionAsync().then(() =&gt; {
        const cr = Promise.coroutine(genFn);
  
        return Promise
          .try(() =&gt; cr(conn))
          .then(res =&gt; {
            return conn.commitAsync().thenReturn(res);
          }, err =&gt; {
            return conn.rollbackAsync()
              .then(() =&gt; logger.info('Transaction successfully rolled back'))
              .catch(e =&gt; logger.error(e))
              .throw(err);
          });
      });
    });
  };
  </code></pre>
  
  <p>A couple of notes here:</p>
  
  <ol>
  <li>The <code>wrap</code> function is just a little piece of wrapper middleware that allows me to use generators/yield in my routes.</li>
  <li>The <code>db</code> module is also just a small wrapper around the popular <code>mysql</code> module, that has been promisified.</li>
  </ol>
  
  <p>What I'd like to do, probably in <code>db.queryAsync</code>, is check if there's a <code>conn</code> object set on the current context (which I'd set around the <code>return Promise...</code> call in <code>db.withTransaction</code>). If so, use that connection to do all subsequent database calls until the context goes out of scope.</p>
  
  <p>Unfortunately, wrapping the <code>return Promise...</code> call in the CLS namespace code didn't allow me to actually return the promise -- it just returned the <code>context</code> object, which is incorrect in my case. It looks like most usages of CLS rely on not actually returning anything from inside the <code>run</code> callback. I also looked at cls-bluebird, but that didn't seem to do what I need it to do, either.</p>
  
  <p>Any ideas? I feel like I'm close, but it's just not all hooking up exactly how I need it to.</p>