Connector hooks are triggered by actions of connectors.
Page Contents

Overview

Connectors are responsible for interacting with the backend systems on behalf of model methods. Connector hooks enable applications to intercept the connector execution.

Two connector hooks are available:

  • ‘before execute’
  • ‘after execute’

before execute

The ‘before execute’ hook is invoked before a connector sends a request to the backend data source.

var connector = MyModel.getDataSource().connector;
connector.observe('before execute', function(ctx, next) {
  // ...
  next();
});

To terminate the invocation, call ctx.end(err, result), for example:

var connector = MyModel.getDataSource().connector;
connector.observe('before execute', function(ctx, next) {
  // ...
  ctx.end(null, cachedResponse);
});

after execute

The ‘after execute’ hook is invoked after the connector receives a response from the backend data source.

connector.observe('after execute', function(ctx, next) {
  // ...
  next();
});

Context

The context object contains information for the hooks to act on. It varies based on the type of connectors. 

Relational database connectors

Relational database connectors include connectors for MySQL, PostgreSQL, SQL Server, Oracle, and so on.

before: {req: {sql: 'SELECT ...', params: [1, 2]}, end: ...}
after: {req: {sql: 'SELECT ...', params: [1, 2]}, res: ..., end: ...}

MongoDB connector

before: {req: {command: ..., params: ...}, end: ...}
after: {req: {...}, res: {...}, end: ...}

Where:

REST connector

before: {req: {...}, end: ...}
after: {req: {...}, res: {...}, end: ...}

Where:

  • req is the request object being passed to request module.
  • res is the response object received from request module.

Connector hooks give you access to the Loopback context object, ctx, that contains information that won’t make it to the end of your method. Typically, at the end of a call you get the body of the response, so the headers are only available in the ctx object.

You have to use the connector hook during application boot; for example:

server/boot/set-headers-in-body.js

module.exports = function(server) {
  var APIConnector;
  APIConnector = server.datasources.APIDataSource.connector;
  return APIConnector.observe('after execute', function(ctx, next) {

  });
};

Now inside this function you have access to this information:

  • Response headers: ctx.res.headers.
  • HTTP response code: ctx.res.body.code.
  • HTTP method of the request: ctx.req.method.

The hook is triggered every time the connector is called, so every request with the REST connector will go through this function.

However, every call to the API won’t go through this hook because it does not call the next() function yet.

To hook every POST request the server makes and put the location from the header inside the body, for example:

module.exports = function(server) {
  var APIConnector;
  APIConnector = server.datasources.APIDataSource.connector;
  return APIConnector.observe('after execute', function(ctx, next) {
    if (ctx.req.method === 'POST') {
      ctx.res.body.location = ctx.res.headers.location;
      return ctx.end(null, ctx, ctx.res.body);
    } else {
      return next();
    }
  });
};

The ctx.end method needs three arguments: err, ctx, and result. The result is what is sent in the end, when the remote method is called.

To avoid most bugs, specify the cases where you touch ctx; for example:

if (ctx.req.method === 'POST'
    && ((ref = ctx.res) != null ? (ref1 = ref.body) != null ? ref1.code : void 0 : void 0) === 200
    && ctx.res.headers.location) {
      // ...
    }

Error handling

There are many uses of a connector hook, such as reformatting responses from the API, useful for APIs that return XML. You can also use a connector hook to handle errors from the API before it comes to your model inside your promise.

If you’re using an API gateway, every error sent may be a HTTP error 502 Bad Gateway when it should be 403 Forbidden. To avoid confusion between server errors and gateway errors, customize error handling in the hook; for example:

module.exports = function(server) {
  var APIConnector;
  APIConnector = server.datasources.APIDataSource.connector;
  return APIConnector.observe('after execute', function(ctx, next) {
    var err, ref, ref1;
    if (/^[5]/.test((ref = ctx.res) != null ? (ref1 = ref.body) != null ? ref1.code : void 0 : void 0)) {
      err = new Error('Error from the API');
      err.status = 403;
      err.message = ctx.res.body.message;
      return ctx.end(err, ctx, ctx.res.body);
    } else {
      return next();
    }
  });
};

SOAP connector

before: {req: {...}, end: ...}
after: {req: {...}, res: {...}, end: ...}

Where:

  • req is the request object being passed to request module.
  • res is the response object received from request module.