May 16, 2018

Enhance Your Loopback Models with Custom mixins

This article puts light on the very useful mixins option of your model.json declaration file.
loopback

Loopback provides a very high-level javascript framework for your backend: prebuilt methods, tables, data validation, routes, etc. Unfortunately its comprehensive documentation is not easy to follow. You will likely miss some part of it and end up re-doing things. With pain.

In this post I want to put light on the very useful mixins option of your model.json declaration file.

Mixins overview

Several models of your application are likely to share common properties, relations, methods, etc. For instance, you may want to add a timestamp or a userstamp to each one your records. Or you want to add computed properties to your model.

Loopback mixins let you factorize any of these actions on models. In other words, any things that you want to define on a model (properties, relations, acl, methods, hooks, etc.) can be defined in a mixin.

Load prebuilt Loopback mixins

You can find on internet prebuilt mixins like for instance the loopback-ds-timestamp-mixin that add the createdAt and updatedAt fields to your model:

  • first install the mixin:

npm i loopback-ds-timestamp-mixin --save
  • then add the mixins property to your server/model-config.json :

{
   "_meta": {
     "sources": [
        "loopback/common/models",
        "loopback/server/models",
        "../common/models",
        "./models"
     ],
     "mixins": [
        "loopback/common/mixins",
        "../node_modules/loopback-ds-timestamp-mixin",
        "../common/mixins"
     ]
  }
}
  • finally in your model.json file, add:

{
   "mixins": {
      "TimeStamp": true
   }
}

Unfortunately there is not a lot of public mixins availables. Fortunately I am going to tell you how to create your own!

Create your own mixin

Public mixins are useful but to harness the power of mixins you will need to create a custom one fitted to your special case.

A mixin is nothing more than a method of two parametersfunction(Model, options):

  • Model is the model on which the mixin will be applied

  • options are the options that may be defined in the model.json under the mixins key. To add a mixin you only need to add mixinName: true. If instead of true you provide an object, then this object is the options parameter of the mixin function.

From that moment on, all the usual hooks and operations on models are possible. For instance you can define:

Especially useful in the mixin context are these two methods:

  • Model.defineProperty to add a property to the model:

Model.defineProperty(propertyName,
 {
  type: String,
  required: required,
 }
);
  • Model.dataSource.defineRelations to add a relation to the model:

Model.dataSource.defineRelations(Model,
 {
  creatorId: {
   type: 'belongsTo',
   model: 'user',
   foreignKey: 'creatorId',
  },
 }
)

Note that you can define properties or relations not only on the current Model but on all the models of your application. When defining a belongsTo relation, you can also directly add the hasMany relation of the corresponding model.

There are many other parameters and methods I may not be aware but I have found these two ones usefull in my own mixins:

  • Model.modelName: returns the name of the model

  • Model.settings: return a javascript object corresponding to the model.json file.

Testing your mixin

If the mixin uses only utils then you should tests your utils as independent methods. If the mixin uses different models then you should test the behavior of the mixin by building a test database and models with app.loopback.createModel in your test file.

Example: create a UserStamp mixin

I got started with mixin because we wanted to add a UserStamp to our models: store the id of the user that performed the action in the database.

Essentially we had to do two things to the Model:

  1. create the relation to the user database;

  2. create a hook before save to add the user info to the instance.

The first point is straight forward:

Model.dataSource.defineRelations(Model, {
    creator: {
      type: 'belongsTo',
      model: 'user',
      foreignKey: 'creatorId',
    },
  });

The second one requires to pass information from the HTTP request context to the hook. In Loopback, this is done by adding data to the context.options field of the beforeRemote hook:

Model.beforeRemote('**', function(context, unused, next) {
    // throw error if required info is not provided
    if (!context.req.currentUser || !context.req.currentUser.id) {
      const noCurrentUserError = new Error(
        'The request does not contain current user information'
      );
      noCurrentUserError.status = 401;
      throw noCurrentUserError;
    
    // put the data into the `options` key
    context.args.options.creatorId = context.req.currentUser.id;
    next();
  })

Then, declaring the options argument:

{ arg: 'options', type: 'object', http: 'optionsFromRequest' },

in your remote method allows you to access this options object. If this is not clear, don’t hesitate to read carefully the documentation.

In any case, you can now in any remote methods have access to the data. We use the before save hook to add them:

Model.observe('before save', function(context, next) {
      if (context.instance) {
        context.instance.creatorId =
          context.instance.creatorId || context.options.creatorId;
      } else {
        context.data.creatorId =
          context.data.creatorId || context.options.creatorId;
      }
      next();
    };
);

Put these three code blocks in a userStamp.js file in a mixin folder. Then add it your model-config.json:

{
  "_meta": {
    "sources": [
      "loopback/common/models",
      "loopback/server/models",
      "../common/models",
      "./models"
    ],
    "mixins": [
      "loopback/common/mixins",
      "../node_modules/loopback-ds-timestamp-mixin",
      "../common/mixins",
      "./mixins"
    ],
    ... 

You can now use your mixin as follows in your model.json:

"mixins": {
    "TimeStamp": true,
    "UserStamp": true
  }

This is it! Here are the links to the corresponding files in my project:

Thanks to Tristan Roussel. 

rboy

Basics in R Programming

You are about to begin a project on R? Before you watch any tutorial, read these basic standards.

blurry street

GAN with Keras: Application to Image Deblurring

A Generative Adversarial Networks tutorial applied to Image Deblurring with the Keras library.

cloud

How to Build a Serverless REST API in 15 Minutes on AWS

Use AWS Lambda to build a Serverless REST API, storing data in S3 and querying it with Athena.