Current context for LoopBack applications, based on node-continuation-local-storage
Go to file
Miroslav Bajtoš 750d8bb5c9 3.0.0
* Rework README, add info from docs site (Miroslav Bajtoš)
 * Drop continuation-local-storage, use cls-hooked (josieusa)
 * Update paid support URL (Siddhi Pai)
 * Add app using loopback-context (Amir Jafarian)
 * Drop support for Node v0.10 and v0.12 (Siddhi Pai)
 * Start the development of the next major version (Siddhi Pai)
 * Update deps to loopback 3.0.0 RC (Miroslav Bajtoš)
 * make warning less ambigious (Carl Fürstenberg)
2017-01-06 10:35:11 +01:00
.github Update paid support URL 2016-12-06 03:08:19 -08:00
browser Rename to loopback-context/LoopBackContext 2016-07-29 09:36:34 +02:00
example Add app using loopback-context 2016-11-25 14:28:01 -05:00
server Drop continuation-local-storage, use cls-hooked 2017-01-03 13:12:28 +01:00
test Drop continuation-local-storage, use cls-hooked 2017-01-03 13:12:28 +01:00
.eslintignore Initial commit 2016-07-28 15:11:12 +02:00
.eslintrc Initial commit 2016-07-28 15:11:12 +02:00
.gitignore Initial commit 2016-07-28 15:11:12 +02:00
.travis.yml Drop continuation-local-storage, use cls-hooked 2017-01-03 13:12:28 +01:00
CHANGES.md 3.0.0 2017-01-06 10:35:11 +01:00
CONTRIBUTING.md Initial commit 2016-07-28 15:11:12 +02:00
README.md Rework README, add info from docs site 2017-01-05 14:59:13 +01:00
package.json 3.0.0 2017-01-06 10:35:11 +01:00

README.md

loopback-context

Current context for LoopBack applications, based on cls-hooked.

WARNING

cls-hooked module uses undocumented AsyncWrap API that was introduced to Node.js relatively recently. While this new API seems to be more reliable than the old async-listener used by continuation-local-storage, there are still cases where the context (local storage) is not preserved correctly. Please consider this risk before using loopback-context.

Known issues

In general, any module that implements a custom task queue or a connection pool is prone to break context storage. This is an inherent problem of continuation local storage that needs to be fixed at lower level - first in Node.js core and then in modules implementing task queues and connection pools.

Installation

$ npm install --save loopback-context cls-hooked

Make sure you are running on a Node.js version supported by this module (^4.5, ^5.10 or ^6.0). When installing, check the output of npm install and make sure there are no engine related warnings.

Usage

Setup cls-hooked

To minimize the likelihood of loosing context in your application, you should ensure that cls-hooked is loaded as the first module of your application, so that it can wrap certain Node.js APIs before any other modules start using these APIs.

Our recommended approach is to add -r cls-hooked to node's list of arguments when starting your LoopBack application.

$ node -r cls-hooked .

If you are using a process manager like strong-pm or pm2, then consult their documentation whether it's possible to configure the arguments used to spawn worker processes. Note that slc run does not support this feature yet, see strong-supervisor#56.

Alternatively, you can add the following line as the first line of your main application file:

require('cls-hooked');

This approach should be compatible with all process managers, including strong-pm. However, we feel that relying on the order of require statements is error-prone.

Configure context propagation

To setup your LoopBack application to create a new context for each incoming HTTP request, configure per-context middleware in your server/middleware.json as follows:

{
  "initial": {
    "loopback-context#per-request": {
    }
  }
}

IMPORTANT: By default, the HTTP req/res objects are not set onto the current context. You need to set enableHttpContext to true to enable automatic population of req/res objects.

{
  "initial": {
    "loopback-context#per-request": {
      "params": {
        "enableHttpContext": true
      }
    }
  }
}

Use the current context

Once youve enabled context propagation, you can access the current context object using LoopBackContext.getCurrentContext(). The context will be available in middleware (if it is loaded after the context middleware), remoting hooks, model hooks, and custom methods.

var LoopBackContext = require('loopback-context');

// ...

MyModel.myMethod = function(cb) {
  var ctx = LoopBackContext.getCurrentContext();
  ctx.get('key');
  ctx.set('key', { foo: 'bar' });
});

Use current authenticated user in remote methods

In advanced use cases, for example when you want to add custom middleware, you have to add the context middleware at the right position in the middleware chain (before the middleware that depends on LoopBackContext.getCurrentContext).

IMPORTANT: LoopBackContext.perRequest() detects the situation when it is invoked multiple times on the same request and returns immediately in subsequent runs.

Here is a snippet using a middleware function to place the currently authenticated user into the context so that remote methods may use it:

server/middleware/store-current-user.js

module.exports = function(options) {
  return function storeCurrentUser(req, res, next) {
    if (!req.accessToken) {
      return next();
    }

    app.models.UserModel.findById(req.accessToken.userId, function(err, user) {
      if (err) {
        return next(err);
      }
      if (!user) {
        return next(new Error('No user with this access token was found.'));
      }
      var loopbackContext = LoopBackContext.getCurrentContext();
      if (loopbackContext) {
        loopbackContext.set('currentUser', user);
      }
      next();
    });
  };
};

server/middleware.json

{
  "initial": {
    "loopback-context#per-request": {}
  },
  "auth": {
    "loopback#token": {}
  },
  "auth:after": {
    "./middleware/set-current-user": {}
  }
}

common/models/YourModel.json

var LoopBackContext = require('loopback-context');
module.exports = function(YourModel) {
  ...
  //remote method
  YourModel.someRemoteMethod = function(arg1, arg2, cb) {
    var ctx = LoopBackContext.getCurrentContext();
    var currentUser = ctx && ctx.get('currentUser');
    console.log('currentUser.username: ', currentUser.username); // voila!
    ...
    cb(null);
  };
  ...
};